feat(gateway): register fabric.register gateway method for live no-restart registration
Recruitment's register-agent step is a plain shell step (no LLM turn), so it cannot invoke the `fabric-register` TOOL (tool only fires inside an agent turn) and there is no `openclaw tools call` CLI. It previously fell back to the standalone bootstrap binary, which writes fabric-identity.json but cannot notify the running plugin -> the new agent's inbound socket only came up after a gateway restart. This adds an in-process gateway method `fabric.register` (scope: operator.write) whose handler runs inbound.addAccount: validates the key, persists identity, and brings the inbound socket up immediately. The script now calls `openclaw gateway call fabric.register --params ...` and only falls back to the bootstrap binary if the method is unavailable. Also resyncs committed dist/ to source (sub-discussion-hook/store + tools/ inbound were source-committed but their dist artifacts were stale). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
60
dist/fabric/index.js
vendored
60
dist/fabric/index.js
vendored
@@ -14,6 +14,8 @@ import { FabricClient } from './src/fabric-client.js';
|
||||
import { IdentityRegistry } from './src/identity.js';
|
||||
import { syncFabricCommands } from './src/command-sync.js';
|
||||
import { PresenceSync } from './src/presence-sync.js';
|
||||
import { SubDiscussionStore } from './src/sub-discussion-store.js';
|
||||
import { registerSubDiscussionHook } from './src/sub-discussion-hook.js';
|
||||
import path from 'node:path';
|
||||
import os from 'node:os';
|
||||
let runtimeRef = null;
|
||||
@@ -40,10 +42,17 @@ export default defineChannelPluginEntry({
|
||||
const centerApiBase = cfg.channels?.fabric?.centerApiBase ?? 'http://localhost:7001/api';
|
||||
const idFile = api.pluginConfig?.identityFilePath ??
|
||||
path.join(os.homedir(), '.openclaw', 'fabric-identity.json');
|
||||
const subDiscussionFile = path.join(os.homedir(), '.openclaw', 'fabric-sub-discussion.json');
|
||||
// tools operate against a default Center; per-account keys come from config
|
||||
const client = new FabricClient(centerApiBase);
|
||||
const identity = new IdentityRegistry(idFile);
|
||||
registerFabricTools({ registerTool: (d) => api.registerTool(d), logger: api.logger }, client, identity);
|
||||
const subDiscussion = new SubDiscussionStore(subDiscussionFile);
|
||||
registerFabricTools({ registerTool: (d) => api.registerTool(d), logger: api.logger }, client, identity, subDiscussion, cfg);
|
||||
// Per-(agent, channel) prompt injection for sub-discussion channels.
|
||||
// Runs as a sibling to PrismFacet's before_prompt_build hook (and
|
||||
// ClawPrompts' fabric-chat-injector); openclaw composes
|
||||
// appendSystemContext from all registered handlers.
|
||||
registerSubDiscussionHook({ on: api.on, logger: api.logger }, subDiscussion, identity);
|
||||
// Cross-plugin API: globalThis.__fabric
|
||||
// Consumed by ClawPrompts' fabric-chat-injector to narrow its prompt
|
||||
// injection to DM-typed channels only. The channel-meta cache is
|
||||
@@ -56,7 +65,25 @@ export default defineChannelPluginEntry({
|
||||
// fall back to "assume DM" — fail closed on unknown.
|
||||
{
|
||||
const _G = globalThis;
|
||||
_G['__fabric'] = { getChannelType };
|
||||
_G['__fabric'] = {
|
||||
getChannelType,
|
||||
// Dynamic-subscription bridges: tools (notably `fabric-register`)
|
||||
// call these to add/remove an account's inbound socket without
|
||||
// a gateway restart. Both delegate to the live FabricInbound
|
||||
// instance via the module-level `inbound` closure variable; the
|
||||
// closures stay valid across gateway_start / gateway_stop
|
||||
// because we re-assign the variable, not the property.
|
||||
addAccount: async (entry) => {
|
||||
if (!inbound)
|
||||
throw new Error('fabric inbound not ready yet (gateway not started?)');
|
||||
await inbound.addAccount(entry);
|
||||
},
|
||||
removeAccount: (agentId) => {
|
||||
if (!inbound)
|
||||
return;
|
||||
inbound.removeAccount(agentId);
|
||||
},
|
||||
};
|
||||
// Flush channel-meta cache when the gateway shuts down so
|
||||
// recently-recorded xType entries don't get lost.
|
||||
api.on('gateway_stop', () => {
|
||||
@@ -65,8 +92,35 @@ export default defineChannelPluginEntry({
|
||||
}
|
||||
catch { /* ignore */ }
|
||||
});
|
||||
api.logger.info('fabric: __fabric cross-plugin API installed (getChannelType)');
|
||||
api.logger.info('fabric: __fabric cross-plugin API installed (getChannelType + addAccount + removeAccount)');
|
||||
}
|
||||
// CLI-invocable live registration, callable from a shell script via
|
||||
// openclaw gateway call fabric.register --params '{"agentId":"…","apiKey":"fak_…"}'
|
||||
// The `fabric-register` TOOL only fires inside an agent turn, and there is
|
||||
// no `openclaw tools call` CLI — so recruitment's `register-agent` script
|
||||
// (a plain shell step, no LLM turn) had to fall back to the standalone
|
||||
// binary, which can't notify the running plugin → needed a gateway
|
||||
// restart. This gateway method runs in-process: inbound.addAccount
|
||||
// validates the key, persists identity, and brings the socket up live —
|
||||
// no restart.
|
||||
api.registerGatewayMethod('fabric.register', async ({ params, respond }) => {
|
||||
const p = (params ?? {});
|
||||
if (!p.agentId || !p.apiKey) {
|
||||
respond(false, { ok: false }, { code: 'INVALID_REQUEST', message: 'agentId and apiKey required' });
|
||||
return;
|
||||
}
|
||||
if (!inbound) {
|
||||
respond(false, { ok: false }, { code: 'UNAVAILABLE', message: 'fabric inbound not ready (gateway still starting?)' });
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await inbound.addAccount({ agentId: p.agentId, fabricApiKey: p.apiKey });
|
||||
respond(true, { ok: true, agentId: p.agentId });
|
||||
}
|
||||
catch (err) {
|
||||
respond(false, { ok: false }, { code: 'UNAVAILABLE', message: `fabric-register failed: ${String(err)}` });
|
||||
}
|
||||
}, { scope: 'operator.write' });
|
||||
api.on('gateway_start', () => {
|
||||
const _G = globalThis;
|
||||
if (_G._fabricInboundStarted)
|
||||
|
||||
Reference in New Issue
Block a user