@phronesis-io/openclaw-eigenflux 0.0.4 → 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 -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 +212 -277
  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,131 +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
- endpoint: server.endpoint,
60
- workdir: server.workdir,
61
- skillPath: (0, config_1.resolveServerSkillPath)(server),
127
+ eigenfluxHome,
62
128
  });
63
129
  let lastAuthPromptKey = null;
64
130
  const resetAuthPromptGate = () => {
65
131
  lastAuthPromptKey = null;
66
132
  };
67
133
  const notifyAuthRequired = async (authEvent) => {
68
- const promptKey = `${authEvent.reason}:${authEvent.credentialsPath}:${authEvent.source || 'unknown'}`;
134
+ const promptKey = `auth_required:${server.name}`;
69
135
  if (lastAuthPromptKey === promptKey) {
70
- logger.debug(`Skipping duplicate auth prompt for server=${server.name}, key=${promptKey}`);
136
+ logger.debug(`Skipping duplicate auth prompt for server=${server.name}`);
71
137
  return;
72
138
  }
73
139
  lastAuthPromptKey = promptKey;
74
- const authState = credentialsLoader.loadAuthState();
75
- await notifier.deliver(buildAuthRequiredMessage(getPromptContext(), {
76
- authEvent,
77
- authState,
78
- }));
140
+ await notifier.deliver((0, agent_prompt_templates_1.buildAuthRequiredPromptTemplate)({ context: getPromptContext() }));
79
141
  };
80
- const pollingClient = new polling_client_1.EigenFluxPollingClient({
81
- apiUrl: server.endpoint,
82
- getAuthState: () => credentialsLoader.loadAuthState(),
83
- 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),
84
146
  logger,
85
147
  onFeedPolled: async (payload) => {
86
148
  resetAuthPromptGate();
87
- await notifier.deliver(buildFeedPayloadMessage(getPromptContext(), payload));
149
+ await notifier.deliver((0, agent_prompt_templates_1.buildFeedPayloadPromptTemplate)(payload, getPromptContext()));
88
150
  },
89
151
  onAuthRequired: notifyAuthRequired,
90
152
  });
91
- const pmPollingClient = new pm_polling_client_1.EigenFluxPmPollingClient({
92
- apiUrl: server.endpoint,
93
- getAuthState: () => credentialsLoader.loadAuthState(),
94
- pollIntervalSec: server.pmPollIntervalSec,
153
+ const streamClient = new stream_client_1.EigenFluxStreamClient({
154
+ serverName: server.name,
155
+ eigenfluxBin: pluginConfig.eigenfluxBin,
95
156
  logger,
96
- onPmFetched: async (payload) => {
157
+ onPmEvent: async (event) => {
97
158
  resetAuthPromptGate();
98
- 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' });
99
163
  },
100
- onAuthRequired: notifyAuthRequired,
101
164
  });
102
165
  return {
103
166
  server,
167
+ routing,
104
168
  credentialsLoader,
105
169
  notifier,
106
- pollingClient,
107
- pmPollingClient,
170
+ feedPoller,
171
+ streamClient,
108
172
  getPromptContext,
109
173
  };
110
174
  }
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) {
175
+ // ─── Command Handler ────────────────────────────────────────────────────────
176
+ function registerCommand(api, logger, pluginConfig, eigenfluxHome, getRuntimes) {
129
177
  if (!api.registerCommand) {
130
178
  logger.warn('registerCommand API unavailable; skipping /eigenflux command registration');
131
179
  return;
@@ -136,6 +184,7 @@ function registerCommand(api, logger, runtimes) {
136
184
  acceptsArgs: true,
137
185
  handler: async (ctx) => {
138
186
  const parsed = parseCommandArgs(ctx.args);
187
+ const runtimes = getRuntimes();
139
188
  if (parsed.command === 'servers') {
140
189
  return {
141
190
  text: buildServersText(runtimes),
@@ -148,27 +197,27 @@ function registerCommand(api, logger, runtimes) {
148
197
  };
149
198
  }
150
199
  const runtime = selection.runtime;
151
- await rememberCurrentCommandRouteIfPossible(ctx, runtime.server, logger);
200
+ await rememberCurrentCommandRouteIfPossible(ctx, runtime, pluginConfig.eigenfluxBin, logger);
152
201
  switch (parsed.command) {
153
202
  case 'auth':
154
203
  return {
155
- text: buildAuthStatusText(runtime.server, runtime.credentialsLoader.loadAuthState()),
204
+ text: buildAuthStatusText(runtime),
156
205
  };
157
206
  case 'profile':
158
207
  return {
159
- text: await buildProfileText(runtime, runtime.credentialsLoader.loadAuthState()),
208
+ text: await buildProfileText(runtime, pluginConfig.eigenfluxBin),
160
209
  };
161
210
  case 'feed':
162
211
  return {
163
- text: await buildFeedText(runtime, runtime.credentialsLoader.loadAuthState()),
212
+ text: await buildFeedText(runtime),
164
213
  };
165
214
  case 'pm':
166
215
  return {
167
- text: await buildPmPollText(runtime, runtime.credentialsLoader.loadAuthState()),
216
+ text: buildPmStatusText(runtime),
168
217
  };
169
218
  case 'here':
170
219
  return {
171
- text: await buildHereText(ctx, runtime.server, logger),
220
+ text: await buildHereText(ctx, runtime, pluginConfig.eigenfluxBin, logger),
172
221
  };
173
222
  default:
174
223
  return {
@@ -200,7 +249,7 @@ function parseCommandArgs(args) {
200
249
  function selectServerRuntime(runtimes, requestedServerName) {
201
250
  if (runtimes.length === 0) {
202
251
  return {
203
- error: 'No EigenFlux servers are configured.',
252
+ error: 'No EigenFlux servers discovered. Ensure eigenflux CLI is configured with at least one server.',
204
253
  };
205
254
  }
206
255
  if (!requestedServerName) {
@@ -222,19 +271,19 @@ function selectServerRuntime(runtimes, requestedServerName) {
222
271
  }
223
272
  function buildServersText(runtimes) {
224
273
  if (runtimes.length === 0) {
225
- return 'No EigenFlux servers are configured.';
274
+ return 'No EigenFlux servers discovered.';
226
275
  }
227
- const defaultRuntime = runtimes[0];
228
276
  return [
229
- 'EigenFlux servers:',
277
+ 'EigenFlux servers (discovered via CLI):',
230
278
  ...runtimes.map((runtime) => {
231
279
  const flags = [
232
- runtime.server.enabled ? 'enabled' : 'disabled',
233
- defaultRuntime?.server.name === runtime.server.name ? 'default' : null,
280
+ runtime.server.current ? 'default' : null,
281
+ runtime.streamClient.isRunning() ? 'streaming' : null,
234
282
  ]
235
283
  .filter(Boolean)
236
284
  .join(', ');
237
- 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}`;
238
287
  }),
239
288
  ].join('\n');
240
289
  }
@@ -248,23 +297,12 @@ function buildHelpText(runtimes) {
248
297
  ? `Available servers: ${runtimes.map((runtime) => runtime.server.name).join(', ')}`
249
298
  : undefined,
250
299
  '',
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.',
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',
268
306
  ]
269
307
  .filter(Boolean)
270
308
  .join('\n');
@@ -287,98 +325,51 @@ function isInternalAgentSessionKey(value) {
287
325
  const parts = trimmed.split(':').filter((part) => part.length > 0);
288
326
  return parts[0]?.toLowerCase() === 'agent' && parts[2]?.toLowerCase() === 'main';
289
327
  }
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;
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);
316
333
  if (typeof ctx.getCurrentConversationBinding === 'function') {
317
334
  try {
318
335
  const binding = await ctx.getCurrentConversationBinding();
319
336
  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;
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;
326
343
  }
327
344
  }
328
345
  catch (error) {
329
346
  logger.debug(`Failed to read current conversation binding: ${error instanceof Error ? error.message : String(error)}`);
330
347
  }
331
348
  }
332
- if (!replyChannel || !replyTo) {
349
+ if (!channel || !to) {
333
350
  return undefined;
334
351
  }
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
- },
352
+ return (0, notification_route_resolver_1.findSessionRouteForBinding)({
353
+ agentId: runtime.routing.agentId,
354
+ channel,
355
+ to,
356
+ accountId,
350
357
  }, 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
358
  }
368
- async function buildHereText(ctx, serverConfig, logger) {
369
- const route = await resolveCurrentCommandRoute(ctx, serverConfig, logger);
359
+ async function buildHereText(ctx, runtime, eigenfluxBin, logger) {
360
+ const route = await resolveCurrentCommandRoute(ctx, runtime, logger);
370
361
  if (!route || route.sessionKey === 'main' || route.sessionKey.endsWith(':main')) {
371
362
  return [
372
- `Unable to resolve the current external session for server=${serverConfig.name}.`,
363
+ `Unable to resolve the current external session for server=${runtime.server.name}.`,
373
364
  'Run `/eigenflux here` inside the target conversation after OpenClaw has already created a session for it.',
374
365
  ].join('\n');
375
366
  }
376
- 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);
377
368
  if (!saved) {
378
- 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.`;
379
370
  }
380
371
  return [
381
- `EigenFlux server ${serverConfig.name} will deliver to this conversation by default:`,
372
+ `EigenFlux server ${runtime.server.name} will deliver to this conversation by default:`,
382
373
  `sessionKey: ${route.sessionKey}`,
383
374
  `agentId: ${route.agentId}`,
384
375
  `channel: ${route.replyChannel ?? 'unknown'}`,
@@ -388,42 +379,52 @@ async function buildHereText(ctx, serverConfig, logger) {
388
379
  .filter(Boolean)
389
380
  .join('\n');
390
381
  }
391
- async function rememberCurrentCommandRouteIfPossible(ctx, serverConfig, logger) {
392
- const route = await resolveCurrentCommandRoute(ctx, serverConfig, logger);
382
+ async function rememberCurrentCommandRouteIfPossible(ctx, runtime, eigenfluxBin, logger) {
383
+ const route = await resolveCurrentCommandRoute(ctx, runtime, logger);
393
384
  if (!route || route.sessionKey === 'main' || route.sessionKey.endsWith(':main')) {
394
385
  return;
395
386
  }
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'}`);
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)}`);
398
402
  }
403
+ else {
404
+ lines.push('- token: unavailable');
405
+ }
406
+ return lines.join('\n');
399
407
  }
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
- });
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() });
411
412
  }
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');
413
+ if (result.kind === 'not_installed') {
414
+ return `EigenFlux CLI not installed (bin=${result.bin}). Install with: ${INSTALL_COMMAND}`;
420
415
  }
421
- catch (error) {
422
- 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}`;
423
418
  }
419
+ return [
420
+ `EigenFlux profile (server=${runtime.server.name}):`,
421
+ '```json',
422
+ safeJsonStringify(result.data),
423
+ '```',
424
+ ].join('\n');
424
425
  }
425
- async function buildFeedText(runtime, authState) {
426
- const result = await runtime.pollingClient.pollOnce({
426
+ async function buildFeedText(runtime) {
427
+ const result = await runtime.feedPoller.pollOnce({
427
428
  notifyFeed: false,
428
429
  notifyAuthRequired: false,
429
430
  });
@@ -436,89 +437,27 @@ async function buildFeedText(runtime, authState) {
436
437
  '```',
437
438
  ].join('\n');
438
439
  case 'auth_required':
439
- return buildAuthRequiredMessage(runtime.getPromptContext(), {
440
- authEvent: result.authEvent,
441
- authState,
442
- });
440
+ return (0, agent_prompt_templates_1.buildAuthRequiredPromptTemplate)({ context: runtime.getPromptContext() });
443
441
  case 'error':
444
442
  return `EigenFlux feed failed for server ${runtime.server.name}: ${result.error.message}`;
445
443
  default:
446
444
  return `EigenFlux feed finished with an unknown result for server ${runtime.server.name}.`;
447
445
  }
448
446
  }
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}.`;
471
- }
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');
480
- }
481
- if (!response.ok) {
482
- throw new Error(`HTTP ${response.status}: ${response.statusText}`);
483
- }
484
- const payload = (await response.json());
485
- if (payload.code !== 0) {
486
- throw new Error(`API error: ${payload.msg}`);
487
- }
488
- return payload;
489
- }
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}`);
497
- }
498
- if (authState.expiresAt) {
499
- lines.push(`- expires_at: ${authState.expiresAt}`);
500
- }
501
- if (authState.status === 'available') {
502
- 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}`);
503
454
  }
504
- else {
505
- lines.push('- token: unavailable');
455
+ if (!running) {
456
+ lines.push('PM stream is not running. Check auth status or restart the service.');
506
457
  }
507
458
  return lines.join('\n');
508
459
  }
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
- }
460
+ // ─── Utilities ──────────────────────────────────────────────────────────────
522
461
  function maskToken(token) {
523
462
  const trimmed = token.trim();
524
463
  if (trimmed.length <= 10) {
@@ -534,8 +473,4 @@ function safeJsonStringify(value) {
534
473
  return String(value);
535
474
  }
536
475
  }
537
- function toServiceIdSegment(name) {
538
- const sanitized = name.trim().toLowerCase().replace(/[^a-z0-9._-]+/gu, '-');
539
- return sanitized || 'default';
540
- }
541
476
  //# sourceMappingURL=index.js.map