Compare commits
2 Commits
25473384d8
...
42228e0a23
| Author | SHA1 | Date | |
|---|---|---|---|
| 42228e0a23 | |||
| 2abd0000e6 |
60
dist/fabric/src/inbound.js
vendored
60
dist/fabric/src/inbound.js
vendored
@@ -1,3 +1,6 @@
|
||||
import { promises as fs } from 'node:fs';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { join } from 'node:path';
|
||||
import { io } from 'socket.io-client';
|
||||
import { dispatchInboundReplyWithBase } from 'openclaw/plugin-sdk/inbound-reply-dispatch';
|
||||
export class FabricInbound {
|
||||
@@ -87,6 +90,48 @@ export class FabricInbound {
|
||||
this.sockets.push(socket);
|
||||
}
|
||||
}
|
||||
// Download a message's attachments to a temp dir using the agent's guild
|
||||
// token; returns local paths/types/urls for the inbound media context.
|
||||
async fetchAttachments(agentId, endpoint, token, m) {
|
||||
const out = { paths: [], types: [], urls: [] };
|
||||
const list = m.attachments ?? [];
|
||||
if (!list.length || !token)
|
||||
return out;
|
||||
const dir = join(tmpdir(), `fabric-media-${agentId}-${m.messageId}`.replace(/[^\w.-]/g, '_'));
|
||||
try {
|
||||
await fs.mkdir(dir, { recursive: true });
|
||||
}
|
||||
catch {
|
||||
return out;
|
||||
}
|
||||
let i = 0;
|
||||
for (const a of list) {
|
||||
try {
|
||||
const abs = a.url.startsWith('http') ? a.url : `${endpoint}${a.url}`;
|
||||
const res = await fetch(abs, { headers: { authorization: `Bearer ${token}` } });
|
||||
if (!res.ok) {
|
||||
this.log.warn(`fabric: attachment fetch ${res.status} ${abs}`);
|
||||
continue;
|
||||
}
|
||||
const buf = Buffer.from(await res.arrayBuffer());
|
||||
const safe = (a.name ?? `file-${i}`).replace(/[^\w.-]/g, '_').slice(0, 120) || `file-${i}`;
|
||||
const p = join(dir, `${i}-${safe}`);
|
||||
await fs.writeFile(p, buf);
|
||||
out.paths.push(p);
|
||||
out.types.push(a.mimeType ||
|
||||
res.headers.get('content-type')?.split(';')[0] ||
|
||||
'application/octet-stream');
|
||||
out.urls.push(abs);
|
||||
i++;
|
||||
}
|
||||
catch (err) {
|
||||
this.log.warn(`fabric: attachment fetch failed agent=${agentId}: ${String(err)}`);
|
||||
}
|
||||
}
|
||||
if (out.paths.length)
|
||||
this.log.info(`fabric: fetched ${out.paths.length} attachment(s) agent=${agentId}`);
|
||||
return out;
|
||||
}
|
||||
async dispatch(agentId, guild, channelId, m, session) {
|
||||
// wakeup === false -> drop (Fabric already decided this agent is silent)
|
||||
if (m.wakeup !== true) {
|
||||
@@ -106,6 +151,10 @@ 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;
|
||||
// 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);
|
||||
const ctxPayload = core.channel.reply.finalizeInboundContext({
|
||||
Body: m.content,
|
||||
BodyForAgent: m.content,
|
||||
@@ -124,8 +173,17 @@ export class FabricInbound {
|
||||
Timestamp: m.createdAt ? Date.parse(m.createdAt) : Date.now(),
|
||||
OriginatingChannel: 'fabric',
|
||||
OriginatingTo: `fabric:${channelId}`,
|
||||
...(media.paths.length
|
||||
? {
|
||||
MediaPaths: media.paths,
|
||||
MediaTypes: media.types,
|
||||
MediaUrls: media.urls,
|
||||
MediaPath: media.paths[0],
|
||||
MediaType: media.types[0],
|
||||
MediaUrl: media.urls[0],
|
||||
}
|
||||
: {}),
|
||||
});
|
||||
const gt = session.guildAccessTokens.find((t) => t.guildNodeId === guild.nodeId)?.token;
|
||||
await dispatchInboundReplyWithBase({
|
||||
cfg: this.cfg,
|
||||
channel: 'fabric',
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
import { promises as fs } from 'node:fs';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { join } from 'node:path';
|
||||
import { io, type Socket } from 'socket.io-client';
|
||||
import { dispatchInboundReplyWithBase } from 'openclaw/plugin-sdk/inbound-reply-dispatch';
|
||||
import type { FabricClient, FabricSession } from './fabric-client.js';
|
||||
@@ -20,6 +23,7 @@ type Core = {
|
||||
|
||||
type Logger = { info: (m: string) => void; warn: (m: string) => void; error?: (m: string) => void };
|
||||
|
||||
type FabricAttachment = { url: string; name?: string; mimeType?: string };
|
||||
type FabricMessage = {
|
||||
messageId: string;
|
||||
seq: number;
|
||||
@@ -27,6 +31,7 @@ type FabricMessage = {
|
||||
authorUserId?: string;
|
||||
createdAt?: string;
|
||||
channelId?: string;
|
||||
attachments?: FabricAttachment[];
|
||||
wakeup?: boolean;
|
||||
};
|
||||
|
||||
@@ -109,6 +114,53 @@ export class FabricInbound {
|
||||
}
|
||||
}
|
||||
|
||||
// Download a message's attachments to a temp dir using the agent's guild
|
||||
// token; returns local paths/types/urls for the inbound media context.
|
||||
private async fetchAttachments(
|
||||
agentId: string,
|
||||
endpoint: string,
|
||||
token: string | undefined,
|
||||
m: FabricMessage,
|
||||
): Promise<{ paths: string[]; types: string[]; urls: string[] }> {
|
||||
const out = { paths: [] as string[], types: [] as string[], urls: [] as string[] };
|
||||
const list = m.attachments ?? [];
|
||||
if (!list.length || !token) return out;
|
||||
const dir = join(tmpdir(), `fabric-media-${agentId}-${m.messageId}`.replace(/[^\w.-]/g, '_'));
|
||||
try {
|
||||
await fs.mkdir(dir, { recursive: true });
|
||||
} catch {
|
||||
return out;
|
||||
}
|
||||
let i = 0;
|
||||
for (const a of list) {
|
||||
try {
|
||||
const abs = a.url.startsWith('http') ? a.url : `${endpoint}${a.url}`;
|
||||
const res = await fetch(abs, { headers: { authorization: `Bearer ${token}` } });
|
||||
if (!res.ok) {
|
||||
this.log.warn(`fabric: attachment fetch ${res.status} ${abs}`);
|
||||
continue;
|
||||
}
|
||||
const buf = Buffer.from(await res.arrayBuffer());
|
||||
const safe = (a.name ?? `file-${i}`).replace(/[^\w.-]/g, '_').slice(0, 120) || `file-${i}`;
|
||||
const p = join(dir, `${i}-${safe}`);
|
||||
await fs.writeFile(p, buf);
|
||||
out.paths.push(p);
|
||||
out.types.push(
|
||||
a.mimeType ||
|
||||
res.headers.get('content-type')?.split(';')[0] ||
|
||||
'application/octet-stream',
|
||||
);
|
||||
out.urls.push(abs);
|
||||
i++;
|
||||
} catch (err) {
|
||||
this.log.warn(`fabric: attachment fetch failed agent=${agentId}: ${String(err)}`);
|
||||
}
|
||||
}
|
||||
if (out.paths.length)
|
||||
this.log.info(`fabric: fetched ${out.paths.length} attachment(s) agent=${agentId}`);
|
||||
return out;
|
||||
}
|
||||
|
||||
private async dispatch(
|
||||
agentId: string,
|
||||
guild: { nodeId: string; endpoint: string },
|
||||
@@ -135,6 +187,13 @@ 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;
|
||||
|
||||
// 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);
|
||||
|
||||
const ctxPayload = core.channel.reply.finalizeInboundContext({
|
||||
Body: m.content,
|
||||
BodyForAgent: m.content,
|
||||
@@ -153,10 +212,18 @@ export class FabricInbound {
|
||||
Timestamp: m.createdAt ? Date.parse(m.createdAt) : Date.now(),
|
||||
OriginatingChannel: 'fabric',
|
||||
OriginatingTo: `fabric:${channelId}`,
|
||||
...(media.paths.length
|
||||
? {
|
||||
MediaPaths: media.paths,
|
||||
MediaTypes: media.types,
|
||||
MediaUrls: media.urls,
|
||||
MediaPath: media.paths[0],
|
||||
MediaType: media.types[0],
|
||||
MediaUrl: media.urls[0],
|
||||
}
|
||||
: {}),
|
||||
});
|
||||
|
||||
const gt = session.guildAccessTokens.find((t) => t.guildNodeId === guild.nodeId)?.token;
|
||||
|
||||
await dispatchInboundReplyWithBase({
|
||||
cfg: this.cfg as never,
|
||||
channel: 'fabric',
|
||||
|
||||
Reference in New Issue
Block a user