Add RunResult type in src/agent/types.ts (success|timeout|cli-error|parse-error). Runner resolves instead of rejects for all error cases. Router handles all four status variants explicitly. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
110 lines
3.2 KiB
TypeScript
110 lines
3.2 KiB
TypeScript
import * as os from "os";
|
|
import { Message, TextChannel } from "discord.js";
|
|
import { DisclawDatabase } from "./db/database";
|
|
import { DisclawConfig } from "./config/loader";
|
|
import { runAgent } from "./agent/runner";
|
|
import { splitForDiscord } from "./discord/split";
|
|
import { sanitizeForDiscord } from "./runtime/sanitize";
|
|
import { ChannelQueue } from "./runtime/channel-queue";
|
|
import type { RunResult } from "./agent/types";
|
|
|
|
const channelQueue = new ChannelQueue();
|
|
|
|
/**
|
|
* Routes a Discord message to the correct agent based on channel_id.
|
|
* Returns true if the message was handled, false if the channel is not
|
|
* mapped to any agent.
|
|
*/
|
|
export async function routeMessage(
|
|
message: Message,
|
|
db: DisclawDatabase,
|
|
config: DisclawConfig,
|
|
managementChannelId: string | null
|
|
): Promise<boolean> {
|
|
const channelId = message.channelId;
|
|
|
|
if (channelId === managementChannelId) {
|
|
return false;
|
|
}
|
|
|
|
const workspace = db.getWorkspaceByChannelId(channelId);
|
|
if (!workspace) {
|
|
return false;
|
|
}
|
|
|
|
const channel = message.channel as TextChannel;
|
|
const sanitizeOpts = {
|
|
repoRoot: workspace.workspace_path,
|
|
home: os.homedir(),
|
|
};
|
|
|
|
await channelQueue.enqueue(channelId, async () => {
|
|
const typingInterval = setInterval(() => {
|
|
channel.sendTyping().catch(() => {});
|
|
}, 5000);
|
|
await channel.sendTyping().catch(() => {});
|
|
|
|
try {
|
|
const history = db.getConversationHistory(workspace.id, 30);
|
|
|
|
const result: RunResult = await runAgent({
|
|
workspacePath: workspace.workspace_path,
|
|
channelName: channel.name,
|
|
userMessage: message.content,
|
|
conversationHistory: history,
|
|
});
|
|
|
|
if (result.status === "success") {
|
|
db.addConversation(
|
|
workspace.id,
|
|
message.id,
|
|
"user",
|
|
message.content,
|
|
message.author.username
|
|
);
|
|
|
|
const sanitizedResponse = sanitizeForDiscord(result.text, sanitizeOpts);
|
|
const chunks = splitForDiscord(sanitizedResponse);
|
|
let firstMsgId: string | null = null;
|
|
|
|
for (const chunk of chunks) {
|
|
const sent = await channel.send(chunk);
|
|
if (!firstMsgId) {
|
|
firstMsgId = sent.id;
|
|
}
|
|
}
|
|
|
|
if (firstMsgId) {
|
|
db.addConversation(
|
|
workspace.id,
|
|
firstMsgId,
|
|
"assistant",
|
|
sanitizedResponse,
|
|
null
|
|
);
|
|
}
|
|
} else if (result.status === "timeout") {
|
|
await channel
|
|
.send(`⏱️ Agent-Timeout nach ${result.timeoutMs / 1000}s`)
|
|
.catch(() => {});
|
|
} else if (result.status === "cli-error") {
|
|
await channel
|
|
.send(sanitizeForDiscord(`❌ CLI-Fehler: ${result.message}`, sanitizeOpts))
|
|
.catch(() => {});
|
|
} else if (result.status === "parse-error") {
|
|
await channel
|
|
.send(`⚠️ Antwort konnte nicht gelesen werden`)
|
|
.catch(() => {});
|
|
}
|
|
} catch (error) {
|
|
const msg = error instanceof Error ? error.message : "Unbekannter Fehler";
|
|
await channel
|
|
.send(sanitizeForDiscord(`Fehler bei der Verarbeitung: ${msg}`, sanitizeOpts))
|
|
.catch(() => {});
|
|
} finally {
|
|
clearInterval(typingInterval);
|
|
}
|
|
});
|
|
|
|
return true;
|
|
}
|