Files
AgentCompany/src/main/prompt-builder/index.ts
T
ZacharyZhang-NY b50716d4e8 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.
2026-04-17 14:48:22 -04:00

80 lines
3.3 KiB
TypeScript

import type { AgentRecord, ApprovalRecord, DocumentRecord, ExecutionMode, GoalRecord, KnowledgeEntryRecord, MeetingRecord, ProjectRecord, SprintRecord, TaskRecord } from "@shared/types";
import type { AppLocale } from "@shared/locale";
import type { ProjectedSkill } from "../skill-types";
import { buildSystemPrompt } from "./system-prompt";
import { renderWakeReason } from "./wake-reasons";
import { getSystemMessages } from "./messages";
export interface PromptContext {
agent: AgentRecord;
apiUrl: string;
apiKey: string;
runId: string;
task: TaskRecord | null;
companyName: string;
locale?: AppLocale;
/** Company execution mode: ai_ceo (fully autonomous) or ai_cofounder (human-reviewed) */
executionMode?: ExecutionMode;
goals: GoalRecord[];
projects: ProjectRecord[];
directReports: AgentRecord[];
chainOfCommand: AgentRecord[];
assignedTasks: TaskRecord[];
allAgents: AgentRecord[];
pendingApprovals: ApprovalRecord[];
tasksAwaitingReview: TaskRecord[];
wakeReason: string | null;
allTasks: TaskRecord[];
recentComments: Array<{ taskId: string; taskTitle: string; authorName: string; body: string; createdAt: string }>;
companyDescription: string;
socialAccounts: Array<{ id: string; platform: string; accountName: string; status: string; requireApproval: boolean }>;
upcomingMeetings?: MeetingRecord[];
relevantKnowledge?: KnowledgeEntryRecord[];
activeSprint?: SprintRecord | null;
recentDocuments?: DocumentRecord[];
allConnectors?: Array<{ id: string; label: string; status: string; enabledModels?: string[] }>;
/** D-07: Handoff markdown from a rotated session, injected as context for the agent */
sessionHandoff?: string | null;
/** Skills visible to this agent via pi's progressive-disclosure model. */
skills?: ProjectedSkill[];
/** All company documents for attachment content resolution in messages */
companyDocuments?: DocumentRecord[];
/** Company-wide skill catalog for capability-aware delegation. */
companySkillCatalog?: Array<{ name: string; slug: string; description: string | null }>;
/** Company workspaces visible to the current run. */
companyWorkspaces?: Array<{ name: string; localPath: string }>;
}
export function buildTaskPrompt(ctx: PromptContext): string {
const m = getSystemMessages(ctx.locale);
const systemPrompt = buildSystemPrompt(ctx);
const parts: string[] = [systemPrompt];
// Volatile per-turn context lives AFTER the cacheable system prefix so the
// Anthropic prompt cache hits across heartbeats. Putting the wall-clock
// time inside the system prompt would invalidate the cache on every call.
const now = new Date();
const dateStr = now.toISOString().split("T")[0];
const timeStr = now.toISOString().split("T")[1]?.slice(0, 5) ?? "00:00";
const dayOfWeek = m.dayNames[now.getUTCDay()] ?? "";
parts.push(`\n${m.currentDateSection(dayOfWeek, dateStr, timeStr)}`);
if (ctx.wakeReason) {
const explanation = renderWakeReason(ctx.wakeReason, {
companyId: ctx.agent.companyId,
agentId: ctx.agent.id,
}, ctx.locale);
parts.push(`\n${m.wakeReasonHeading}\n${explanation}\n`);
}
parts.push(`\n${m.timeAwarenessSection}`);
if (ctx.task) {
parts.push(m.immediateTaskSection(ctx.task.title, ctx.task.description, ctx.task.priority));
} else {
parts.push(m.heartbeatFallbackSection);
}
return parts.join("");
}