fix(plugin): refresh guild token per dispatch (fixes attachment 401)
Guild access tokens are short-lived (~15 min); the inbound socket survives via socket.io reconnect but the token captured at connect time goes stale, so attachment downloads (and reply posts) start 401ing on long-lived agents. Re-login with the agent's Fabric API key on a short TTL and use the fresh token for fetch + post. Verified live: 'fabric: fetched 1 attachment(s)' now succeeds where it previously logged 'attachment fetch 401'. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
31
dist/fabric/src/inbound.js
vendored
31
dist/fabric/src/inbound.js
vendored
@@ -12,6 +12,35 @@ export class FabricInbound {
|
||||
accounts;
|
||||
sockets = [];
|
||||
seen = new Set();
|
||||
// Guild access tokens are short-lived (~15 min). The socket survives via
|
||||
// socket.io reconnect, but the token captured at connect time goes stale,
|
||||
// so HTTP calls (attachment download, posting the reply) start 401ing.
|
||||
// Re-login per agent on a short TTL to keep a fresh token.
|
||||
tokenCache = new Map();
|
||||
static TOKEN_TTL_MS = 8 * 60 * 1000;
|
||||
// Return a fresh guild access token for the agent, re-authenticating with
|
||||
// the agent's Fabric API key when the cached session is stale. Falls back
|
||||
// to the connect-time session token if re-login fails.
|
||||
async freshGuildToken(agentId, guildNodeId, fallback) {
|
||||
const pick = (s) => s.guildAccessTokens.find((t) => t.guildNodeId === guildNodeId)?.token;
|
||||
const now = Date.now();
|
||||
const cached = this.tokenCache.get(agentId);
|
||||
if (cached && now - cached.at < FabricInbound.TOKEN_TTL_MS) {
|
||||
return pick(cached.session) ?? pick(fallback);
|
||||
}
|
||||
const apiKey = this.identity.findByAgentId(agentId)?.fabricApiKey;
|
||||
if (apiKey) {
|
||||
try {
|
||||
const s = await this.client.agentLogin(apiKey);
|
||||
this.tokenCache.set(agentId, { session: s, at: now });
|
||||
return pick(s) ?? pick(fallback);
|
||||
}
|
||||
catch (err) {
|
||||
this.log.warn(`fabric: token refresh failed agent=${agentId}: ${String(err)}`);
|
||||
}
|
||||
}
|
||||
return pick(fallback);
|
||||
}
|
||||
constructor(core, // PluginRuntime
|
||||
cfg, // OpenClawConfig
|
||||
client, identity, log, accounts = []) {
|
||||
@@ -151,7 +180,7 @@ export class FabricInbound {
|
||||
const storePath = core.channel.session.resolveStorePath(cfg.session?.store, {
|
||||
agentId: route.agentId,
|
||||
});
|
||||
const gt = session.guildAccessTokens.find((t) => t.guildNodeId === guild.nodeId)?.token;
|
||||
const gt = await this.freshGuildToken(agentId, guild.nodeId, session);
|
||||
// Fetch any uploaded files for the agent: download to a temp dir and
|
||||
// hand openclaw local MediaPaths (+types) so the model receives them.
|
||||
const media = await this.fetchAttachments(agentId, guild.endpoint, gt, m);
|
||||
|
||||
Reference in New Issue
Block a user