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:
@@ -38,6 +38,40 @@ type FabricMessage = {
|
||||
export class FabricInbound {
|
||||
private sockets: Socket[] = [];
|
||||
private seen = new Set<string>();
|
||||
// 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.
|
||||
private tokenCache = new Map<string, { session: FabricSession; at: number }>();
|
||||
private static readonly 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.
|
||||
private async freshGuildToken(
|
||||
agentId: string,
|
||||
guildNodeId: string,
|
||||
fallback: FabricSession,
|
||||
): Promise<string | undefined> {
|
||||
const pick = (s: FabricSession) =>
|
||||
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(
|
||||
private readonly core: unknown, // PluginRuntime
|
||||
@@ -188,7 +222,7 @@ export class FabricInbound {
|
||||
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.
|
||||
|
||||
Reference in New Issue
Block a user