feat(plugin): fabric-guild-list + fabric-channel-set-purpose tools + purpose on existing tools
Adds two agent-facing tools that close the discoverability loop:
- fabric-guild-list — enumerates guilds the agent belongs to with
name + purpose + status (no api calls beyond the existing agentLogin
response). Optional nameFilter/purposeFilter for narrowing.
- fabric-channel-set-purpose — PATCH /api/channels/:id { purpose }
so agents can backfill or update an existing channel's purpose.
Extends existing tools:
- fabric-channel-list now returns purpose on each row.
- create-{chat,work,report,discussion}-channel accept optional purpose.
FabricClient + FabricSession type changes carry the new field through.
Manifest contracts.tools updated (jiti loader needs both manifest entry
and onStartup activation to register).
Lets workflows that previously needed hardcoded channel ids instead say
'find a guild whose purpose mentions debate, then a channel of x_type
announce whose purpose covers public debate broadcasts.'
This commit is contained in:
84
dist/fabric/src/presence-sync.js
vendored
Normal file
84
dist/fabric/src/presence-sync.js
vendored
Normal file
@@ -0,0 +1,84 @@
|
||||
/**
|
||||
* presence-sync — read each connected agent's HF status (via the
|
||||
* cross-plugin `globalThis.__hfAgentStatus.get(agentId)` exposed by
|
||||
* HarborForge.OpenclawPlugin) and push diffs to Fabric.Backend.Guild
|
||||
* `PUT /agents/:userId/presence` so the backend can apply busy-discard
|
||||
* on `announce`-type channel deliveries.
|
||||
*
|
||||
* Push model: we only PUT when an agent's status actually changes
|
||||
* (since the last push). The HF-side accessor has its own TTL cache
|
||||
* to absorb the every-30s polling.
|
||||
*
|
||||
* If HF plugin isn't loaded (`__hfAgentStatus` undefined), the loop
|
||||
* is a no-op — Fabric backend defaults presence to 'unknown' which is
|
||||
* treated as not-busy. Announce-channel delivery still works; busy
|
||||
* filtering simply doesn't kick in.
|
||||
*/
|
||||
export class PresenceSync {
|
||||
logger;
|
||||
timer = null;
|
||||
lastStatus = new Map(); // by agentId
|
||||
accounts = new Map();
|
||||
constructor(logger) {
|
||||
this.logger = logger;
|
||||
}
|
||||
setAccounts(accounts) {
|
||||
this.accounts.clear();
|
||||
for (const a of accounts)
|
||||
this.accounts.set(a.agentId, a);
|
||||
}
|
||||
start(intervalMs = 30_000) {
|
||||
if (this.timer)
|
||||
return;
|
||||
this.timer = setInterval(() => {
|
||||
this.tick().catch((err) => this.logger.warn(`fabric: presence-sync error: ${String(err)}`));
|
||||
}, intervalMs);
|
||||
// run once immediately so initial state lands fast
|
||||
void this.tick();
|
||||
}
|
||||
stop() {
|
||||
if (this.timer) {
|
||||
clearInterval(this.timer);
|
||||
this.timer = null;
|
||||
}
|
||||
}
|
||||
async tick() {
|
||||
const bridge = globalThis['__hfAgentStatus'];
|
||||
if (!bridge || typeof bridge.get !== 'function')
|
||||
return; // HF plugin not loaded — skip
|
||||
for (const [agentId, acct] of this.accounts) {
|
||||
let status;
|
||||
try {
|
||||
status = await bridge.get(agentId);
|
||||
}
|
||||
catch {
|
||||
continue;
|
||||
}
|
||||
if (!status)
|
||||
continue;
|
||||
if (this.lastStatus.get(agentId) === status)
|
||||
continue; // no change → no PUT
|
||||
try {
|
||||
const url = `${acct.guildBaseUrl.replace(/\/$/, '')}/agents/${encodeURIComponent(acct.fabricUserId)}/presence`;
|
||||
const res = await fetch(url, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'content-type': 'application/json',
|
||||
'x-api-key': acct.fabricApiKey,
|
||||
},
|
||||
body: JSON.stringify({ status, source: 'hf-plugin' }),
|
||||
});
|
||||
if (res.ok) {
|
||||
this.lastStatus.set(agentId, status);
|
||||
this.logger.info(`fabric: presence-sync ${agentId} → ${status}`);
|
||||
}
|
||||
else {
|
||||
this.logger.warn(`fabric: presence-sync PUT ${agentId} failed: ${res.status}`);
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
this.logger.warn(`fabric: presence-sync PUT ${agentId} threw: ${String(err)}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user