fix(presence-sync): use /api prefix + Bearer guildAccessToken (not x-api-key)
Two layered bugs in the presence-sync loop, both causing every PUT to
fail forever in prod:
1. **Missing /api prefix.** URL was `${guildBaseUrl}/agents/<id>/presence`
but the guild backend sets a global prefix 'api' in main.ts
`setGlobalPrefix('api')`. Every other REST call in this plugin
(channel.ts channels list, fabric-client.ts postMessage, canvas)
already prepends /api/ — only presence-sync missed it. Returned 404
"Cannot PUT /agents/...".
2. **Wrong auth scheme.** Plugin sent `x-api-key: <fabricApiKey>`, but
the endpoint sits behind the global APP_GUARD = ApiKeyGuard, which
actually expects `Authorization: Bearer <guildAccessToken>` (despite
its name — confusing naming on the backend side). With /api added,
error became 401 "missing bearer token". Confirmed by `docker exec
fabric-backend-guild grep APP_GUARD /app/dist/app.module.js` and
manual curl: Bearer guild token → 200 OK.
**Fix**
- presence-sync.ts: do agent-login on demand to obtain a fresh
guildAccessToken, cache it per-agent for 13 min (under the 15-min
JWT TTL), use it as Bearer for the PUT. 401 response invalidates
the cache so the next tick re-logs-in. Pushes are gated on status
changes (rare), so the login overhead is negligible.
- inbound.ts: firstGuildEndpointByAgent → firstGuildByAgent storing
both endpoint and nodeId (presence-sync needs nodeId to pick the
right token out of guildAccessTokens[]).
- index.ts: pass FabricClient to PresenceSync constructor.
**Verified in sim**
After restart, gateway log shows `fabric: presence-sync recruiter →
idle` (200 OK), zero failed PUTs, where previously it would log a 404
every ~5s per agent.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -281,17 +281,25 @@ export class FabricInbound {
|
||||
agentId: string;
|
||||
fabricUserId: string;
|
||||
guildBaseUrl: string;
|
||||
guildNodeId: string;
|
||||
fabricApiKey: string;
|
||||
}> {
|
||||
const out: Array<{ agentId: string; fabricUserId: string; guildBaseUrl: string; fabricApiKey: string }> = [];
|
||||
const out: Array<{
|
||||
agentId: string;
|
||||
fabricUserId: string;
|
||||
guildBaseUrl: string;
|
||||
guildNodeId: string;
|
||||
fabricApiKey: string;
|
||||
}> = [];
|
||||
for (const entry of this.identity.list()) {
|
||||
if (!entry.fabricUserId) continue;
|
||||
const presenceGuildUrl = this.firstGuildEndpointByAgent.get(entry.agentId);
|
||||
if (!presenceGuildUrl) continue;
|
||||
const presenceGuild = this.firstGuildByAgent.get(entry.agentId);
|
||||
if (!presenceGuild) continue;
|
||||
out.push({
|
||||
agentId: entry.agentId,
|
||||
fabricUserId: entry.fabricUserId,
|
||||
guildBaseUrl: presenceGuildUrl,
|
||||
guildBaseUrl: presenceGuild.endpoint,
|
||||
guildNodeId: presenceGuild.nodeId,
|
||||
fabricApiKey: entry.fabricApiKey,
|
||||
});
|
||||
}
|
||||
@@ -299,17 +307,20 @@ export class FabricInbound {
|
||||
}
|
||||
|
||||
// Filled by connectAgent for each (agent, guild). Tracks ONLY the first
|
||||
// guild per agent (used as the presence-push target).
|
||||
private firstGuildEndpointByAgent = new Map<string, string>();
|
||||
// guild per agent (used as the presence-push target). Stores both
|
||||
// endpoint and nodeId — presence-sync needs both: endpoint to build
|
||||
// the URL, nodeId to pick the matching guildAccessToken from a fresh
|
||||
// agent-login response.
|
||||
private firstGuildByAgent = new Map<string, { endpoint: string; nodeId: string }>();
|
||||
|
||||
private async connectAgent(agentId: string, session: FabricSession): Promise<void> {
|
||||
const selfUserId = session.user.id;
|
||||
// First-guild capture for presence-sync push target. session.guilds is
|
||||
// already in priority order from Center; we take the first one with a
|
||||
// valid endpoint and stop. Multi-guild presence is a future concern.
|
||||
if (!this.firstGuildEndpointByAgent.has(agentId)) {
|
||||
if (!this.firstGuildByAgent.has(agentId)) {
|
||||
const firstGuild = session.guilds.find((g) => typeof g.endpoint === 'string' && g.endpoint.length > 0);
|
||||
if (firstGuild) this.firstGuildEndpointByAgent.set(agentId, firstGuild.endpoint);
|
||||
if (firstGuild) this.firstGuildByAgent.set(agentId, { endpoint: firstGuild.endpoint, nodeId: firstGuild.nodeId });
|
||||
}
|
||||
for (const g of session.guilds) {
|
||||
const tok = session.guildAccessTokens.find((t) => t.guildNodeId === g.nodeId)?.token;
|
||||
|
||||
Reference in New Issue
Block a user