From 26c933fad7dd9a249394fe3cc689824c257ce7ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9B=B7=E7=94=B5=E8=8A=BD=E8=A1=A3?= Date: Wed, 22 Apr 2026 20:34:16 -0400 Subject: [PATCH] Reduce runtime thread system noise --- .../agent-runtime/runtime-thread-projector.ts | 22 ++- tests/unit/runtime-thread-projector.test.ts | 172 ++++++++++++++++++ 2 files changed, 184 insertions(+), 10 deletions(-) create mode 100644 tests/unit/runtime-thread-projector.test.ts diff --git a/src/main/agent-runtime/runtime-thread-projector.ts b/src/main/agent-runtime/runtime-thread-projector.ts index 87078904..ee58cee3 100644 --- a/src/main/agent-runtime/runtime-thread-projector.ts +++ b/src/main/agent-runtime/runtime-thread-projector.ts @@ -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; } } diff --git a/tests/unit/runtime-thread-projector.test.ts b/tests/unit/runtime-thread-projector.test.ts new file mode 100644 index 00000000..1a6dfb7d --- /dev/null +++ b/tests/unit/runtime-thread-projector.test.ts @@ -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 { + 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(); + } + }); +});