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

+5 -48
View File
@@ -1,19 +1,18 @@
/**
* Message Normalization Layer
*
* Normalizes messages and comments before they are injected into agent prompts.
* Normalizes comments before they are injected into agent prompts.
* Prevents malformed, empty, or excessively long content from degrading
* model output quality. Inspired by Reference/src/utils/messages.ts.
*
* Applied at the prompt-builder boundary — the last point before content
* enters the model's context window.
*
* Inter-agent messages no longer flow through a prompt block; they are
* projected directly into the recipient's pi session transcript. See
* `agent-runtime/inbox-projection.ts`.
*/
import type { AgentMessageRecord } from "@shared/types";
/** Maximum body length for a single message in prompt context. */
const MAX_MESSAGE_BODY_LENGTH = 800;
/** Maximum comment body length in prompt context. */
const MAX_COMMENT_BODY_LENGTH = 500;
@@ -36,48 +35,6 @@ function truncate(text: string, maxLen: number): string {
return text.slice(0, maxLen - 1) + "…";
}
/**
* Normalize agent messages before prompt injection.
* - Removes empty-body messages
* - Cleans control characters
* - Truncates overly long bodies
* - Deduplicates consecutive identical messages from the same sender
*/
export function normalizeMessagesForPrompt(
messages: AgentMessageRecord[],
limit: number = 15,
): AgentMessageRecord[] {
const result: AgentMessageRecord[] = [];
let prevKey = "";
for (const msg of messages) {
const cleaned = cleanText(msg.body);
if (cleaned.length === 0) continue;
// Deduplicate consecutive identical messages from the same sender
const key = `${msg.fromAgentId}:${cleaned}`;
if (key === prevKey) continue;
prevKey = key;
result.push({
...msg,
body: truncate(cleaned, MAX_MESSAGE_BODY_LENGTH),
subject: msg.subject ? truncate(cleanText(msg.subject), 120) : msg.subject,
});
if (result.length >= limit) break;
}
return result;
}
// ---------------------------------------------------------------------------
// Transcript Repair — for session handoff / resume scenarios
// Inspired by Reference/src/utils/messages.ts tool pairing logic.
// Applied when session state is restored, not at every message send.
// ---------------------------------------------------------------------------
export interface CommentRecord {
taskId: string;
taskTitle: string;