refactor(messaging): project inter-agent messages into pi session transcript

Inter-agent messages previously entered the recipient's context as a
separate prompt block (recentMessages), rebuilt every turn. This double-
charged tokens and kept messages outside pi's native AgentMessage stream.

Now at prepareRun: pull the recipient's unread inbox, project each
AgentMessageRecord into a pi user-role AgentMessage, persist to
SqliteSessionStore, and mark delivered. The recipient reads messages
as natural conversation turns in the transcript.

- db: listAgentInboxUnread + markAgentInboxDelivered (transactional)
- agent-runtime/inbox-projection.ts: AgentMessageRecord -> pi AgentMessage
- runtime-bridge.prepareRun: inject + deliver before loadMessages
- prompt-builder: delete recentMessages context block + buildMessagesSection
- message-normalize: drop unused normalizeMessagesForPrompt
- delete prompt-builder/attachment-resolver.ts (orphan after section removal)

Send path (api-server/routes-messages, ipc, UI) unchanged — inbox table
remains the UI source. Transactional delivery marking prevents duplicate
injection across runs. Failed append -> no mark -> retry on next run.
This commit is contained in:
ZacharyZhang-NY committed 2026-04-17 14:48:22 -04:00
1 parent 5547c29491
commit b50716d4e8
11 files changed
+254 -175

No files matched your search

+1 -2
View File
@@ -11,7 +11,7 @@ import type { AppDatabase } from "../database";
import type { ProjectedSkill } from "../skill-types";
import type { ConnectorId, ExecutionMode } from "@shared/types";
import type { WorkspaceLockMode, EnqueueRunPayload } from "./types";
import { normalizeMessagesForPrompt, normalizeCommentsForPrompt } from "../message-normalize";
import { normalizeCommentsForPrompt } from "../message-normalize";
import { getConnectorModelPolicy, validateConnectorModelSelection } from "../connector-model-policy";
export interface RunDispatchParams {
@@ -291,7 +291,6 @@ export function buildRunPayload(params: RunDispatchParams, deps: TurnContextDeps
status: account.status,
requireApproval: account.requireApproval,
})),
recentMessages: normalizeMessagesForPrompt(db.listAgentMessages(companyId, { agentId: agent.id, limit: 20 })),
upcomingMeetings: filterUpcomingMeetings(snapshot.meetings, companyId, agent.id),
relevantKnowledge: filterRelevantKnowledge(snapshot.knowledgeBase, companyId),
activeSprint: snapshot.sprints.find((sprint) => sprint.companyId === companyId && sprint.status === "active") ?? null,