Reduce runtime thread system noise
This commit is contained in:
1 parent
a4af4a866a
commit
26c933fad7
2 files changed
+184
-10
No files matched your search
@@ -4,7 +4,7 @@ import {
|
||||
ensureAgentCommunicationThread,
|
||||
ensureTaskThread,
|
||||
} from "@main/thread-integration/thread-helpers";
|
||||
import { extractTextBody } from "./agent-event-projection";
|
||||
import { extractMessageText } from "./agent-event-projection";
|
||||
import type { InboundCorrelationEntry } from "./inbound-correlation-registry";
|
||||
|
||||
type RuntimeThreadEnvelope = {
|
||||
@@ -45,18 +45,20 @@ export class RuntimeThreadProjector {
|
||||
message: RuntimeThreadMessage | undefined,
|
||||
): void {
|
||||
if (!message?.role) return;
|
||||
|
||||
const thread = this.resolveThreadForRun(envelope);
|
||||
if (!thread) return;
|
||||
if (message.role !== "assistant" && message.role !== "user") return;
|
||||
|
||||
try {
|
||||
const body = extractTextBody(message.content);
|
||||
const body = extractMessageText(message);
|
||||
const actor = this.resolveThreadEventActor(envelope, message);
|
||||
if (!actor) return;
|
||||
if (body.length === 0) return;
|
||||
const thread = this.resolveThreadForRun(envelope);
|
||||
if (!thread) return;
|
||||
this.deps.db.core.threads.addThreadEvent({
|
||||
threadId: thread.id,
|
||||
actorId: actor.actorId,
|
||||
actorType: actor.actorType,
|
||||
eventType: message.role === "toolResult" ? "system_note" : "message",
|
||||
eventType: "message",
|
||||
body: body.length > 4000 ? `${body.slice(0, 3997)}...` : body,
|
||||
metadataJson: JSON.stringify({
|
||||
runId: envelope.runId,
|
||||
@@ -66,7 +68,7 @@ export class RuntimeThreadProjector {
|
||||
});
|
||||
} catch (error) {
|
||||
this.deps.logger.warn(
|
||||
`[runtime-bridge] failed to project agent-event into thread ${thread.id}: ${
|
||||
`[runtime-bridge] failed to project agent-event for run ${envelope.runId}: ${
|
||||
error instanceof Error ? error.message : String(error)
|
||||
}`,
|
||||
);
|
||||
@@ -144,7 +146,7 @@ export class RuntimeThreadProjector {
|
||||
private resolveThreadEventActor(
|
||||
envelope: { runId: string; agentId: string },
|
||||
message: { role?: string; timestamp?: number },
|
||||
): { actorId: string; actorType: "agent" | "user" | "system"; sourceMessageId: string | null } {
|
||||
): { actorId: string; actorType: "agent" | "user" | "system"; sourceMessageId: string | null } | null {
|
||||
if (message.role === "user") {
|
||||
const correlation = this.deps.lookupInboundCorrelation(envelope.runId, message.timestamp);
|
||||
if (correlation) {
|
||||
@@ -160,11 +162,11 @@ export class RuntimeThreadProjector {
|
||||
sourceMessageId: correlation.messageId,
|
||||
};
|
||||
}
|
||||
return { actorId: "user", actorType: "user", sourceMessageId: null };
|
||||
return null;
|
||||
}
|
||||
if (message.role === "assistant") {
|
||||
return { actorId: envelope.agentId, actorType: "agent", sourceMessageId: null };
|
||||
}
|
||||
return { actorId: envelope.agentId, actorType: "system", sourceMessageId: null };
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,172 @@
|
||||
import { join } from "node:path";
|
||||
import { tmpdir } from "node:os";
|
||||
import { mkdtemp, rm } from "node:fs/promises";
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
|
||||
import { AppDatabase } from "../../src/main/database";
|
||||
import { RuntimeThreadProjector } from "../../src/main/agent-runtime/runtime-thread-projector";
|
||||
import { findTaskThread } from "../../src/main/thread-integration/thread-helpers";
|
||||
|
||||
const tempDirs: string[] = [];
|
||||
|
||||
async function createTempDir(prefix: string): Promise<string> {
|
||||
const dir = await mkdtemp(join(tmpdir(), prefix));
|
||||
tempDirs.push(dir);
|
||||
return dir;
|
||||
}
|
||||
|
||||
afterEach(async () => {
|
||||
await Promise.all(tempDirs.splice(0).map((dir) => rm(dir, { recursive: true, force: true })));
|
||||
});
|
||||
|
||||
function textMessage(role: string, text: string, timestamp = Date.now()) {
|
||||
return {
|
||||
role,
|
||||
timestamp,
|
||||
content: [{ type: "text", text }],
|
||||
};
|
||||
}
|
||||
|
||||
describe("RuntimeThreadProjector", () => {
|
||||
it("projects assistant messages without internal prompt and tool noise", async () => {
|
||||
const profileDir = await createTempDir("agentcompany-thread-projector-");
|
||||
const db = new AppDatabase(profileDir);
|
||||
db.init();
|
||||
|
||||
try {
|
||||
const companyId = db.core.company.saveCompany({
|
||||
name: "Thread Co",
|
||||
description: "Thread projection test",
|
||||
status: "active",
|
||||
});
|
||||
const agentId = db.core.agents.saveAgent({
|
||||
companyId,
|
||||
name: "CTO",
|
||||
role: "CTO",
|
||||
title: "Chief Technology Officer",
|
||||
status: "active",
|
||||
reportsTo: null,
|
||||
connectorId: "openai-codex",
|
||||
workspaceId: null,
|
||||
model: null,
|
||||
capabilities: "Engineering",
|
||||
budgetMonthlyUsd: 100,
|
||||
});
|
||||
const taskId = db.core.tasks.saveTask({
|
||||
companyId,
|
||||
title: "Coordinate launch",
|
||||
description: "Keep the launch thread readable.",
|
||||
assigneeAgentId: agentId,
|
||||
priority: "high",
|
||||
status: "todo",
|
||||
});
|
||||
|
||||
const projector = new RuntimeThreadProjector({
|
||||
db,
|
||||
logger: { warn: () => {} },
|
||||
resolveAgentName: () => "CTO",
|
||||
lookupInboundCorrelation: () => null,
|
||||
});
|
||||
const envelope = { runId: "run-1", agentId, companyId, taskId };
|
||||
|
||||
projector.recordThreadEvent(envelope, textMessage("user", "Internal initial user turn."));
|
||||
projector.recordThreadEvent(envelope, textMessage("toolResult", "Tool output."));
|
||||
projector.recordThreadEvent(envelope, {
|
||||
role: "assistant",
|
||||
timestamp: Date.now(),
|
||||
content: [{ type: "toolCall", name: "bash" }],
|
||||
});
|
||||
projector.recordThreadEvent(envelope, textMessage("assistant", "Launch owner update sent to Product."));
|
||||
|
||||
const thread = findTaskThread(db, taskId, companyId);
|
||||
if (!thread) throw new Error("Expected task thread to be created.");
|
||||
const events = db.core.threads.listThreadEvents(thread.id, companyId, { limit: 10 });
|
||||
|
||||
expect(events.map((event) => event.body)).toEqual(["Launch owner update sent to Product."]);
|
||||
expect(events[0].actorId).toBe(agentId);
|
||||
expect(events[0].actorType).toBe("agent");
|
||||
expect(events[0].eventType).toBe("message");
|
||||
} finally {
|
||||
db.close();
|
||||
}
|
||||
});
|
||||
|
||||
it("keeps live correlated inbound messages in the thread", async () => {
|
||||
const profileDir = await createTempDir("agentcompany-thread-inbound-");
|
||||
const db = new AppDatabase(profileDir);
|
||||
db.init();
|
||||
|
||||
try {
|
||||
const companyId = db.core.company.saveCompany({
|
||||
name: "Inbound Co",
|
||||
description: "Inbound projection test",
|
||||
status: "active",
|
||||
});
|
||||
const agentId = db.core.agents.saveAgent({
|
||||
companyId,
|
||||
name: "CEO",
|
||||
role: "CEO",
|
||||
title: "Chief Executive Officer",
|
||||
status: "active",
|
||||
reportsTo: null,
|
||||
connectorId: "openai-codex",
|
||||
workspaceId: null,
|
||||
model: null,
|
||||
capabilities: "Leadership",
|
||||
budgetMonthlyUsd: 100,
|
||||
});
|
||||
const peerId = db.core.agents.saveAgent({
|
||||
companyId,
|
||||
name: "CTO",
|
||||
role: "CTO",
|
||||
title: "Chief Technology Officer",
|
||||
status: "active",
|
||||
reportsTo: agentId,
|
||||
connectorId: "openai-codex",
|
||||
workspaceId: null,
|
||||
model: null,
|
||||
capabilities: "Engineering",
|
||||
budgetMonthlyUsd: 100,
|
||||
});
|
||||
const taskId = db.core.tasks.saveTask({
|
||||
companyId,
|
||||
title: "Answer engineering",
|
||||
description: "Respond in the thread.",
|
||||
assigneeAgentId: agentId,
|
||||
priority: "high",
|
||||
status: "todo",
|
||||
});
|
||||
const timestamp = Date.now();
|
||||
const projector = new RuntimeThreadProjector({
|
||||
db,
|
||||
logger: { warn: () => {} },
|
||||
resolveAgentName: () => "CEO",
|
||||
lookupInboundCorrelation: () => ({
|
||||
messageId: "message-1",
|
||||
fromAgentId: peerId,
|
||||
senderKind: "agent",
|
||||
piTimestamp: timestamp,
|
||||
}),
|
||||
});
|
||||
|
||||
projector.recordThreadEvent(
|
||||
{ runId: "run-1", agentId, companyId, taskId },
|
||||
textMessage("user", "Can you approve the deployment?", timestamp),
|
||||
);
|
||||
|
||||
const thread = findTaskThread(db, taskId, companyId);
|
||||
if (!thread) throw new Error("Expected task thread to be created.");
|
||||
const events = db.core.threads.listThreadEvents(thread.id, companyId, { limit: 10 });
|
||||
|
||||
expect(events).toHaveLength(1);
|
||||
expect(events[0]).toMatchObject({
|
||||
actorId: peerId,
|
||||
actorType: "agent",
|
||||
eventType: "message",
|
||||
body: "Can you approve the deployment?",
|
||||
});
|
||||
} finally {
|
||||
db.close();
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user