feat(plugin): record non-wakeup messages as session history (no model)
Previously a non-wakeup message returned immediately and was fully discarded — the agent kept zero record of it, so when later woken in a discuss/work channel it replied without the conversation context. Now non-wakeup messages are ingested into the agent's OpenClaw session via recordInboundSession (createIfMissing) WITHOUT dispatch: the real model is not invoked and nothing is sent back to Fabric. This is correct for the turn engine — only the woken speaker emits a normal message or /no-reply; non-woken agents stay silent — while still giving the agent full channel context whenever it IS woken. Verified live: report-channel (all recipients wakeup=false) message logs 'recorded (no wakeup, history only)' with 0 dispatch/deliver/ posted; wakeup path unchanged. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -16,7 +16,16 @@ import type { IdentityRegistry } from './identity.js';
|
||||
type Core = {
|
||||
channel: {
|
||||
routing: { resolveAgentRoute: (p: unknown) => { agentId: string; sessionKey: string; accountId?: string } };
|
||||
session: { resolveStorePath: (store: unknown, o: { agentId: string }) => string };
|
||||
session: {
|
||||
resolveStorePath: (store: unknown, o: { agentId: string }) => string;
|
||||
recordInboundSession: (p: {
|
||||
storePath: string;
|
||||
sessionKey: string;
|
||||
ctx: unknown;
|
||||
createIfMissing?: boolean;
|
||||
onRecordError: (e: unknown) => void;
|
||||
}) => Promise<unknown>;
|
||||
};
|
||||
reply: { finalizeInboundContext: (p: Record<string, unknown>) => unknown };
|
||||
};
|
||||
};
|
||||
@@ -202,13 +211,6 @@ export class FabricInbound {
|
||||
m: FabricMessage,
|
||||
session: FabricSession,
|
||||
): Promise<void> {
|
||||
// wakeup === false -> drop (Fabric already decided this agent is silent)
|
||||
if (m.wakeup !== true) {
|
||||
this.log.info(`fabric: drop (no wakeup) agent=${agentId} channel=${channelId}`);
|
||||
return;
|
||||
}
|
||||
this.log.info(`fabric: dispatch agent=${agentId} channel=${channelId}`);
|
||||
|
||||
const core = this.core as Core & Record<string, unknown>;
|
||||
const cfg = this.cfg as { session?: { store?: unknown } };
|
||||
try {
|
||||
@@ -222,13 +224,7 @@ export class FabricInbound {
|
||||
agentId: route.agentId,
|
||||
});
|
||||
|
||||
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);
|
||||
|
||||
const ctxPayload = core.channel.reply.finalizeInboundContext({
|
||||
const baseCtx: Record<string, unknown> = {
|
||||
Body: m.content,
|
||||
BodyForAgent: m.content,
|
||||
RawBody: m.content,
|
||||
@@ -246,6 +242,39 @@ export class FabricInbound {
|
||||
Timestamp: m.createdAt ? Date.parse(m.createdAt) : Date.now(),
|
||||
OriginatingChannel: 'fabric',
|
||||
OriginatingTo: `fabric:${channelId}`,
|
||||
};
|
||||
|
||||
// Non-wakeup: Fabric has already decided this agent is NOT the speaker
|
||||
// this round. Do NOT run the model and do NOT send anything back — the
|
||||
// discuss/work turn engine expects silence from non-woken agents (only
|
||||
// the woken speaker emits a normal message or /no-reply). We still
|
||||
// record the message into the agent's session so it has the full
|
||||
// channel conversation as context whenever it IS later woken.
|
||||
if (m.wakeup !== true) {
|
||||
const ctxPayload = core.channel.reply.finalizeInboundContext(baseCtx);
|
||||
await core.channel.session.recordInboundSession({
|
||||
storePath,
|
||||
sessionKey: route.sessionKey,
|
||||
ctx: ctxPayload,
|
||||
createIfMissing: true,
|
||||
onRecordError: (err: unknown) =>
|
||||
this.log.warn(`fabric: history record failed agent=${agentId}: ${String(err)}`),
|
||||
});
|
||||
this.log.info(
|
||||
`fabric: recorded (no wakeup, history only) agent=${agentId} channel=${channelId}`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
this.log.info(`fabric: dispatch agent=${agentId} channel=${channelId}`);
|
||||
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);
|
||||
|
||||
const ctxPayload = core.channel.reply.finalizeInboundContext({
|
||||
...baseCtx,
|
||||
// Provide ONLY local paths. The guild file URL is on a private host
|
||||
// (e.g. localhost); openclaw's SSRF guard blocks re-fetching it, so
|
||||
// passing MediaUrls is both redundant (we already downloaded the
|
||||
|
||||
Reference in New Issue
Block a user