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.
80 lines
3.3 KiB
TypeScript
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("");
|
|
}
|