refactor(agent): replace monolithic prompt with section builders (#44)
Extract the ~227-line template literal in buildSystemPrompt() into 11 composable section builders with a data-driven tool registry. Adds PromptMode support (full/minimal/none) for future use without drowning in conditionals. Fixes the unsafe `as` cast on catalog entries with proper type narrowing. Same persona, same tools, same behavior — just structured. Co-authored-by: Nicholai <nicholaivogelfilms@gmail.com>
This commit is contained in:
parent
8b34becbeb
commit
3e5b351b19
@ -48,6 +48,7 @@ export async function POST(req: Request): Promise<Response> {
|
||||
currentPage,
|
||||
memories,
|
||||
pluginSections,
|
||||
mode: "full",
|
||||
}),
|
||||
messages: await convertToModelMessages(body.messages),
|
||||
tools: { ...agentTools, ...githubTools },
|
||||
|
||||
@ -1,295 +1,515 @@
|
||||
import { compassCatalog } from "@/lib/agent/render/catalog"
|
||||
import type { PromptSection } from "@/lib/agent/plugins/types"
|
||||
|
||||
interface PromptContext {
|
||||
readonly userName: string
|
||||
readonly userRole: string
|
||||
readonly currentPage?: string
|
||||
readonly projectId?: string
|
||||
readonly memories?: string
|
||||
readonly pluginSections?: ReadonlyArray<PromptSection>
|
||||
// --- types ---
|
||||
|
||||
type PromptMode = "full" | "minimal" | "none"
|
||||
|
||||
type ToolCategory =
|
||||
| "data"
|
||||
| "navigation"
|
||||
| "ui"
|
||||
| "memory"
|
||||
| "github"
|
||||
| "skills"
|
||||
| "feedback"
|
||||
|
||||
interface ToolMeta {
|
||||
readonly name: string
|
||||
readonly summary: string
|
||||
readonly category: ToolCategory
|
||||
readonly adminOnly?: true
|
||||
}
|
||||
|
||||
interface PromptContext {
|
||||
readonly userName: string
|
||||
readonly userRole: string
|
||||
readonly currentPage?: string
|
||||
readonly memories?: string
|
||||
readonly pluginSections?: ReadonlyArray<PromptSection>
|
||||
readonly mode?: PromptMode
|
||||
}
|
||||
|
||||
interface DerivedState {
|
||||
readonly mode: PromptMode
|
||||
readonly page: string
|
||||
readonly isAdmin: boolean
|
||||
readonly catalogComponents: string
|
||||
readonly tools: ReadonlyArray<ToolMeta>
|
||||
}
|
||||
|
||||
// --- tool registry ---
|
||||
|
||||
const TOOL_REGISTRY: ReadonlyArray<ToolMeta> = [
|
||||
{
|
||||
name: "queryData",
|
||||
summary:
|
||||
"Query the database for customers, vendors, projects, " +
|
||||
"invoices, bills, schedule tasks, or record details. " +
|
||||
"Pass a queryType and optional search/id/limit.",
|
||||
category: "data",
|
||||
},
|
||||
{
|
||||
name: "navigateTo",
|
||||
summary:
|
||||
"Navigate to a page. Side-effect tool — one call is " +
|
||||
"enough. Do NOT also call queryData or generateUI. " +
|
||||
"Valid paths: /dashboard, /dashboard/projects, " +
|
||||
"/dashboard/projects/{id}, " +
|
||||
"/dashboard/projects/{id}/schedule, " +
|
||||
"/dashboard/customers, /dashboard/vendors, " +
|
||||
"/dashboard/financials, /dashboard/people, " +
|
||||
"/dashboard/files. If the page doesn't exist, " +
|
||||
"tell the user what's available.",
|
||||
category: "navigation",
|
||||
},
|
||||
{
|
||||
name: "showNotification",
|
||||
summary:
|
||||
"Show a toast notification. Use sparingly — only " +
|
||||
"for confirmations or important alerts.",
|
||||
category: "ui",
|
||||
},
|
||||
{
|
||||
name: "generateUI",
|
||||
summary:
|
||||
"Render a rich interactive dashboard (tables, charts, " +
|
||||
"stats, forms). Workflow: queryData first, then " +
|
||||
"generateUI with dataContext. For follow-ups, call " +
|
||||
"again — the system sends incremental patches.",
|
||||
category: "ui",
|
||||
},
|
||||
{
|
||||
name: "queryGitHub",
|
||||
summary:
|
||||
"Query GitHub for commits, commit_diff, pull_requests, " +
|
||||
"issues, contributors, milestones, or repo_stats. " +
|
||||
"Use DataTable for tabular results, StatCard for " +
|
||||
"repo overview, BarChart for activity viz.",
|
||||
category: "github",
|
||||
},
|
||||
{
|
||||
name: "createGitHubIssue",
|
||||
summary:
|
||||
"Create a GitHub issue. Fields: title (required), " +
|
||||
"body (markdown, required), labels (optional), " +
|
||||
"assignee (optional), milestone (optional number). " +
|
||||
"Always confirm title/body/labels with the user first.",
|
||||
category: "github",
|
||||
},
|
||||
{
|
||||
name: "rememberContext",
|
||||
summary:
|
||||
"Save a preference, decision, fact, or workflow to " +
|
||||
"persistent memory. Types: preference, workflow, " +
|
||||
"fact, decision. Proactively save when user shares " +
|
||||
"something worth retaining — don't ask permission.",
|
||||
category: "memory",
|
||||
},
|
||||
{
|
||||
name: "recallMemory",
|
||||
summary:
|
||||
"Search saved memories. Use when user asks " +
|
||||
'"do you remember..." or you need a past preference.',
|
||||
category: "memory",
|
||||
},
|
||||
{
|
||||
name: "installSkill",
|
||||
summary:
|
||||
'Install a skill from GitHub (skills.sh format). ' +
|
||||
'Source: "owner/repo/skill-name" or "owner/repo". ' +
|
||||
"Confirm with the user before installing.",
|
||||
category: "skills",
|
||||
adminOnly: true,
|
||||
},
|
||||
{
|
||||
name: "listInstalledSkills",
|
||||
summary:
|
||||
"List installed skills and their enabled/disabled status.",
|
||||
category: "skills",
|
||||
},
|
||||
{
|
||||
name: "toggleInstalledSkill",
|
||||
summary: "Enable or disable a skill by its plugin ID.",
|
||||
category: "skills",
|
||||
},
|
||||
{
|
||||
name: "uninstallSkill",
|
||||
summary:
|
||||
"Permanently remove an installed skill. " +
|
||||
"Confirm before uninstalling.",
|
||||
category: "skills",
|
||||
adminOnly: true,
|
||||
},
|
||||
{
|
||||
name: "saveInterviewFeedback",
|
||||
summary:
|
||||
"Save completed UX interview results. Call only " +
|
||||
"after finishing the interview flow. Saves to DB " +
|
||||
'and creates a GitHub issue tagged "user-feedback".',
|
||||
category: "feedback",
|
||||
},
|
||||
]
|
||||
|
||||
// categories included in minimal mode
|
||||
const MINIMAL_CATEGORIES: ReadonlySet<ToolCategory> = new Set([
|
||||
"data",
|
||||
"navigation",
|
||||
"ui",
|
||||
])
|
||||
|
||||
// --- derived state ---
|
||||
|
||||
function extractDescription(
|
||||
entry: unknown,
|
||||
): string {
|
||||
if (
|
||||
typeof entry === "object" &&
|
||||
entry !== null &&
|
||||
"description" in entry &&
|
||||
typeof (entry as Record<string, unknown>).description ===
|
||||
"string"
|
||||
) {
|
||||
return (entry as Record<string, unknown>)
|
||||
.description as string
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
function computeDerivedState(ctx: PromptContext): DerivedState {
|
||||
const mode = ctx.mode ?? "full"
|
||||
const page = ctx.currentPage ?? "dashboard"
|
||||
const isAdmin = ctx.userRole === "admin"
|
||||
|
||||
const catalogComponents = Object.entries(
|
||||
compassCatalog.data.components,
|
||||
)
|
||||
.map(([name, def]) => `- ${name}: ${extractDescription(def)}`)
|
||||
.join("\n")
|
||||
|
||||
const tools =
|
||||
mode === "none"
|
||||
? []
|
||||
: TOOL_REGISTRY.filter((t) => {
|
||||
if (t.adminOnly && !isAdmin) return false
|
||||
if (mode === "minimal") {
|
||||
return MINIMAL_CATEGORIES.has(t.category)
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
return { mode, page, isAdmin, catalogComponents, tools }
|
||||
}
|
||||
|
||||
// --- section builders ---
|
||||
|
||||
function buildIdentity(mode: PromptMode): ReadonlyArray<string> {
|
||||
const line =
|
||||
"You are Dr. Slab Diggems, the AI assistant built " +
|
||||
"into Compass — a construction project management platform."
|
||||
if (mode === "none") return [line]
|
||||
return [line + " You are reliable, direct, and always ready to help."]
|
||||
}
|
||||
|
||||
function buildUserContext(
|
||||
ctx: PromptContext,
|
||||
state: DerivedState,
|
||||
): ReadonlyArray<string> {
|
||||
if (state.mode === "none") return []
|
||||
return [
|
||||
"## User Context",
|
||||
`- Name: ${ctx.userName}`,
|
||||
`- Role: ${ctx.userRole}`,
|
||||
`- Current page: ${state.page}`,
|
||||
]
|
||||
}
|
||||
|
||||
function buildMemoryContext(
|
||||
ctx: PromptContext,
|
||||
mode: PromptMode,
|
||||
): ReadonlyArray<string> {
|
||||
if (mode !== "full") return []
|
||||
return [
|
||||
"## What You Remember About This User",
|
||||
ctx.memories ||
|
||||
"No memories yet. When the user shares preferences, " +
|
||||
"decisions, or important facts, use rememberContext " +
|
||||
"to save them.",
|
||||
]
|
||||
}
|
||||
|
||||
function buildFirstInteraction(
|
||||
mode: PromptMode,
|
||||
page: string,
|
||||
): ReadonlyArray<string> {
|
||||
if (mode !== "full") return []
|
||||
|
||||
const suggestions = [
|
||||
'"I can pull up your active projects, recent invoices, ' +
|
||||
'or outstanding vendor bills."',
|
||||
'"Need to check on a schedule, find a customer, or ' +
|
||||
'navigate somewhere? Just ask."',
|
||||
'"I can show you charts, tables, and project summaries ' +
|
||||
'— or just answer a quick question."',
|
||||
'"Want to check the project\'s development status? I can ' +
|
||||
'show you recent commits, PRs, issues, and contributor activity."',
|
||||
'"I can also conduct a quick UX interview if you\'d like ' +
|
||||
'to share feedback about Compass."',
|
||||
]
|
||||
|
||||
return [
|
||||
"## First Interaction",
|
||||
"When a user first messages you or seems unsure what " +
|
||||
"to ask, proactively offer what you can do. For example:",
|
||||
...suggestions.map((s) => `- ${s}`),
|
||||
"",
|
||||
"Tailor suggestions to the user's current page. " +
|
||||
(page.includes("project")
|
||||
? "They're on a projects page — lead with project-specific help."
|
||||
: page.includes("financial")
|
||||
? "They're on financials — lead with invoice and billing capabilities."
|
||||
: page.includes("customer")
|
||||
? "They're on customers — lead with customer lookup and management."
|
||||
: page.includes("vendor")
|
||||
? "They're on vendors — lead with vendor and bill capabilities."
|
||||
: "If they're on the dashboard, offer a broad overview."),
|
||||
]
|
||||
}
|
||||
|
||||
function buildDomainKnowledge(
|
||||
mode: PromptMode,
|
||||
): ReadonlyArray<string> {
|
||||
if (mode !== "full") return []
|
||||
return [
|
||||
"## Domain",
|
||||
"You help with construction project management: tracking " +
|
||||
"projects, schedules, customers, vendors, invoices, and " +
|
||||
"vendor bills. You understand construction terminology " +
|
||||
"(phases, change orders, submittals, RFIs, punch lists, etc).",
|
||||
]
|
||||
}
|
||||
|
||||
function buildToolDocs(
|
||||
tools: ReadonlyArray<ToolMeta>,
|
||||
): ReadonlyArray<string> {
|
||||
if (tools.length === 0) return []
|
||||
return [
|
||||
"## Available Tools",
|
||||
...tools.map(
|
||||
(t) =>
|
||||
`- **${t.name}**: ${t.summary}` +
|
||||
(t.adminOnly ? " *(admin only)*" : ""),
|
||||
),
|
||||
]
|
||||
}
|
||||
|
||||
function buildCatalogSection(
|
||||
mode: PromptMode,
|
||||
catalogComponents: string,
|
||||
): ReadonlyArray<string> {
|
||||
if (mode !== "full") return []
|
||||
return [
|
||||
"## generateUI Components",
|
||||
"Available component types for generateUI:",
|
||||
catalogComponents,
|
||||
"",
|
||||
"For follow-up requests while a dashboard is visible, call " +
|
||||
"generateUI again — the system sends incremental patches.",
|
||||
"",
|
||||
"## Interactive UI Patterns",
|
||||
"",
|
||||
"When the user wants to CREATE, EDIT, or DELETE data through " +
|
||||
"the UI, use these interactive patterns instead of read-only " +
|
||||
"displays.",
|
||||
"",
|
||||
"### Creating records with Form",
|
||||
"Wrap inputs in a Form component. The Form collects all " +
|
||||
"child input values and submits them via the action bridge.",
|
||||
"",
|
||||
"Example — create a customer:",
|
||||
"```",
|
||||
'Form(formId="new-customer", action="customer.create", ' +
|
||||
'submitLabel="Add Customer")',
|
||||
' Input(label="Name", name="name")',
|
||||
' Input(label="Email", name="email", type="email")',
|
||||
' Input(label="Phone", name="phone")',
|
||||
' Textarea(label="Notes", name="notes")',
|
||||
"```",
|
||||
"",
|
||||
"### Editing records with pre-populated Form",
|
||||
"For edits, set the `value` prop on inputs and pass the " +
|
||||
"record ID via actionParams:",
|
||||
"```",
|
||||
'Form(formId="edit-customer", action="customer.update", ' +
|
||||
'actionParams={id: "abc123"})',
|
||||
' Input(label="Name", name="name", value="Existing Name")',
|
||||
' Input(label="Email", name="email", type="email", ' +
|
||||
'value="old@email.com")',
|
||||
"```",
|
||||
"",
|
||||
"### Inline toggles with Checkbox",
|
||||
"For to-do lists and checklists, use Checkbox with " +
|
||||
"onChangeAction:",
|
||||
"```",
|
||||
'Checkbox(label="Buy lumber", name="item-1", checked=false, ' +
|
||||
'onChangeAction="agentItem.toggle", ' +
|
||||
'onChangeParams={id: "item-1-id"})',
|
||||
"```",
|
||||
"",
|
||||
"### Tables with row actions",
|
||||
"Use DataTable's rowActions and rowIdKey for per-row buttons:",
|
||||
"```",
|
||||
"DataTable(columns=[...], data=[...], rowIdKey=\"id\", " +
|
||||
'rowActions=[{label: "Delete", action: "customer.delete", ' +
|
||||
'variant: "danger"}])',
|
||||
"```",
|
||||
"",
|
||||
"### Available mutation actions",
|
||||
"- customer.create, customer.update, customer.delete",
|
||||
"- vendor.create, vendor.update, vendor.delete",
|
||||
"- invoice.create, invoice.update, invoice.delete",
|
||||
"- vendorBill.create, vendorBill.update, vendorBill.delete",
|
||||
"- schedule.create, schedule.update, schedule.delete",
|
||||
"- agentItem.create, agentItem.update, agentItem.delete, " +
|
||||
"agentItem.toggle",
|
||||
"",
|
||||
"### When to use interactive vs read-only",
|
||||
'- User says "show me" / "list" / "what are" -> read-only ' +
|
||||
"DataTable, charts",
|
||||
'- User says "add" / "create" / "new" -> Form with action',
|
||||
'- User says "edit" / "update" / "change" -> pre-populated Form',
|
||||
'- User says "delete" / "remove" -> DataTable with delete ' +
|
||||
"rowAction",
|
||||
'- User says "to-do" / "checklist" / "task list" -> ' +
|
||||
"Checkbox with onChangeAction",
|
||||
]
|
||||
}
|
||||
|
||||
function buildInterviewProtocol(
|
||||
mode: PromptMode,
|
||||
): ReadonlyArray<string> {
|
||||
if (mode !== "full") return []
|
||||
return [
|
||||
"## User Experience Interviews",
|
||||
"When a user explicitly asks to give feedback, share their " +
|
||||
"experience, or participate in a UX interview, conduct a " +
|
||||
"conversational interview:",
|
||||
"",
|
||||
"1. Ask ONE question at a time. Wait for the answer.",
|
||||
"2. Cover these areas (adapt to the user's role):",
|
||||
" - How they use Compass day-to-day",
|
||||
" - What works well for them",
|
||||
" - Pain points or frustrations",
|
||||
" - Features they wish existed",
|
||||
" - How Compass compares to tools they've used before",
|
||||
" - Bottlenecks in their workflow",
|
||||
"3. Follow up on interesting answers with deeper questions.",
|
||||
"4. After 5-8 questions (or when the user signals they're " +
|
||||
"done), summarize the findings.",
|
||||
"5. Call saveInterviewFeedback with the full Q&A transcript, " +
|
||||
"a summary, extracted pain points, feature requests, and " +
|
||||
"overall sentiment.",
|
||||
"6. Thank the user for their time.",
|
||||
"",
|
||||
"Do NOT start an interview unless the user explicitly asks. " +
|
||||
"Never pressure users into giving feedback.",
|
||||
]
|
||||
}
|
||||
|
||||
function buildGitHubGuidance(
|
||||
mode: PromptMode,
|
||||
): ReadonlyArray<string> {
|
||||
if (mode !== "full") return []
|
||||
return [
|
||||
"## GitHub API Usage",
|
||||
"Be respectful of GitHub API rate limits. Avoid making " +
|
||||
"excessive queries in a single conversation. Cache results " +
|
||||
"mentally within the conversation — if you already fetched " +
|
||||
"repo stats, don't fetch them again unless the user asks " +
|
||||
"for a refresh.",
|
||||
"",
|
||||
"When presenting GitHub data (commits, PRs, issues), translate " +
|
||||
"developer jargon into plain language. Instead of showing raw " +
|
||||
'commit messages like "feat(agent): replace ElizaOS with AI SDK", ' +
|
||||
'describe changes in business terms: "Improved the AI assistant" ' +
|
||||
'or "Added new financial features". Your audience is construction ' +
|
||||
"professionals, not developers.",
|
||||
]
|
||||
}
|
||||
|
||||
function buildGuidelines(
|
||||
mode: PromptMode,
|
||||
): ReadonlyArray<string> {
|
||||
if (mode === "none") return []
|
||||
|
||||
const core = [
|
||||
"## Guidelines",
|
||||
"- Be concise and helpful. Construction managers are busy.",
|
||||
"- ACT FIRST, don't ask. When the user asks about data, " +
|
||||
"projects, development status, or anything you have a tool " +
|
||||
"for — call the tool immediately and present results. Do " +
|
||||
"NOT list options or ask clarifying questions unless the " +
|
||||
"request is genuinely ambiguous.",
|
||||
"- If you don't know something, say so rather than guessing.",
|
||||
"- Never fabricate data. Only present what queryData returns.",
|
||||
]
|
||||
|
||||
if (mode === "minimal") return core
|
||||
|
||||
return [
|
||||
...core,
|
||||
'- "How\'s development going?" means fetch repo_stats and ' +
|
||||
'recent commits right now, not "Would you like to see ' +
|
||||
'commits or PRs?"',
|
||||
"- When asked about data, use queryData to fetch real " +
|
||||
"information.",
|
||||
"- For navigation requests, use navigateTo immediately.",
|
||||
"- After navigating, be brief but warm. A short, friendly " +
|
||||
"confirmation is all that's needed — don't describe the " +
|
||||
"page layout.",
|
||||
"- For data display, prefer generateUI over plain text tables.",
|
||||
"- Use metric and imperial units as appropriate for construction.",
|
||||
"- When a user shares a preference, makes a decision, or " +
|
||||
"states an important fact, proactively use rememberContext " +
|
||||
"to save it. Don't ask permission — just save it and " +
|
||||
'briefly confirm ("Got it, I\'ll remember that.").',
|
||||
]
|
||||
}
|
||||
|
||||
function buildPluginSections(
|
||||
sections: ReadonlyArray<PromptSection> | undefined,
|
||||
mode: PromptMode,
|
||||
): ReadonlyArray<string> {
|
||||
if (mode !== "full") return []
|
||||
if (!sections?.length) return []
|
||||
return [
|
||||
"## Installed Skills",
|
||||
"",
|
||||
...sections.map((s) => `### ${s.heading}\n${s.content}`),
|
||||
]
|
||||
}
|
||||
|
||||
// --- assembler ---
|
||||
|
||||
export function buildSystemPrompt(ctx: PromptContext): string {
|
||||
const catalogComponents = Object.entries(
|
||||
compassCatalog.data.components
|
||||
)
|
||||
.map(
|
||||
([name, def]) =>
|
||||
`- ${name}: ${(def as { description?: string }).description ?? ""}`
|
||||
)
|
||||
.join("\n")
|
||||
const state = computeDerivedState(ctx)
|
||||
|
||||
return `You are Dr. Slab Diggems, the AI assistant built into Compass — a \
|
||||
construction project management platform. You are reliable, \
|
||||
direct, and always ready to help.
|
||||
const sections: ReadonlyArray<ReadonlyArray<string>> = [
|
||||
buildIdentity(state.mode),
|
||||
buildUserContext(ctx, state),
|
||||
buildMemoryContext(ctx, state.mode),
|
||||
buildFirstInteraction(state.mode, state.page),
|
||||
buildDomainKnowledge(state.mode),
|
||||
buildToolDocs(state.tools),
|
||||
buildCatalogSection(state.mode, state.catalogComponents),
|
||||
buildInterviewProtocol(state.mode),
|
||||
buildGitHubGuidance(state.mode),
|
||||
buildGuidelines(state.mode),
|
||||
buildPluginSections(ctx.pluginSections, state.mode),
|
||||
]
|
||||
|
||||
## User Context
|
||||
- Name: ${ctx.userName}
|
||||
- Role: ${ctx.userRole}
|
||||
- Current page: ${ctx.currentPage ?? "dashboard"}
|
||||
${ctx.projectId ? `- Active project ID: ${ctx.projectId}` : ""}
|
||||
|
||||
## What You Remember About This User
|
||||
${ctx.memories || "No memories yet. When the user shares preferences, decisions, or important facts, use rememberContext to save them."}
|
||||
|
||||
## First Interaction
|
||||
When a user first messages you or seems unsure what to ask, \
|
||||
proactively offer what you can do. For example:
|
||||
- "I can pull up your active projects, recent invoices, or \
|
||||
outstanding vendor bills."
|
||||
- "Need to check on a schedule, find a customer, or navigate \
|
||||
somewhere? Just ask."
|
||||
- "I can show you charts, tables, and project summaries — or \
|
||||
just answer a quick question."
|
||||
- "Want to check the project's development status? I can show \
|
||||
you recent commits, PRs, issues, and contributor activity."
|
||||
- "I can also conduct a quick UX interview if you'd like to \
|
||||
share feedback about Compass."
|
||||
|
||||
Tailor suggestions to the user's current page. If they're on the \
|
||||
projects page, offer project-specific help. If they're on \
|
||||
finances, lead with invoice and billing capabilities.
|
||||
|
||||
## Domain
|
||||
You help with construction project management: tracking projects, \
|
||||
schedules, customers, vendors, invoices, and vendor bills. You \
|
||||
understand construction terminology (phases, change orders, \
|
||||
submittals, RFIs, punch lists, etc).
|
||||
|
||||
## Available Tools
|
||||
|
||||
### queryData
|
||||
Query the application database using predefined query types. \
|
||||
Pass a natural language description and the system will match it \
|
||||
to available queries. Good for looking up customers, vendors, \
|
||||
projects, invoices, tasks, and other records.
|
||||
|
||||
### navigateTo
|
||||
Navigate the user to a page in the application. Use this when \
|
||||
the user asks to "go to", "show me", "open", or "navigate to" \
|
||||
something. Available paths:
|
||||
- /dashboard - main dashboard
|
||||
- /dashboard/projects - all projects
|
||||
- /dashboard/projects/{id} - specific project detail
|
||||
- /dashboard/projects/{id}/schedule - project schedule
|
||||
- /dashboard/customers - customer management
|
||||
- /dashboard/vendors - vendor management
|
||||
- /dashboard/financials - invoices and bills
|
||||
- /dashboard/people - team members
|
||||
- /dashboard/files - project files
|
||||
|
||||
ONLY use paths from this list. If the user asks for a page that \
|
||||
doesn't exist, tell them what's available instead of guessing.
|
||||
|
||||
IMPORTANT navigation behavior:
|
||||
- When navigating, ONLY call navigateTo. Do NOT also call \
|
||||
queryData or generateUI — the destination page already \
|
||||
displays its own data.
|
||||
- After navigating, be brief but warm. For example: "Taking \
|
||||
you to customers now!" or "On it — heading to the schedule." \
|
||||
Don't describe the page layout or columns — the user can see \
|
||||
it. A short, friendly confirmation is all that's needed.
|
||||
- navigateTo is a side-effect tool. One call is enough.
|
||||
|
||||
### showNotification
|
||||
Show a toast notification to the user. Use sparingly -- only for \
|
||||
confirmations or important alerts.
|
||||
|
||||
### generateUI
|
||||
Generate a rich interactive UI dashboard in the main content \
|
||||
area. Use when the user wants to see structured data \
|
||||
(tables, charts, stats, forms, comparisons, dashboards).
|
||||
|
||||
WORKFLOW:
|
||||
1. First call queryData to fetch the data the user needs
|
||||
2. Then call generateUI with a description of the layout and \
|
||||
pass the fetched data as dataContext
|
||||
|
||||
The UI will render progressively in the main dashboard area \
|
||||
while chat moves to the sidebar panel.
|
||||
|
||||
Available component types:
|
||||
${catalogComponents}
|
||||
|
||||
For follow-up requests while a dashboard is visible, call \
|
||||
generateUI again — the system will send incremental patches \
|
||||
to the existing UI rather than rebuilding from scratch.
|
||||
|
||||
### queryGitHub
|
||||
Query the GitHub repository for development status. Query types:
|
||||
- **commits** - Recent commits. Use DataTable with columns: sha, \
|
||||
message, author, date.
|
||||
- **pull_requests** - Open/closed/all PRs. Use DataTable with \
|
||||
columns: number, title, author, state, labels.
|
||||
- **issues** - Open/closed/all issues. Filter by labels. Use \
|
||||
DataTable with columns: number, title, author, state, labels.
|
||||
- **contributors** - Contributor list with commit counts. Use \
|
||||
DataTable or BarChart for activity visualization.
|
||||
- **milestones** - Project milestones with progress. Use DataTable \
|
||||
with columns: title, state, openIssues, closedIssues, dueOn.
|
||||
- **repo_stats** - Repository overview. Use StatCard components \
|
||||
for stars, forks, open issues, watchers.
|
||||
|
||||
### createGitHubIssue
|
||||
Create a new GitHub issue. Fields: title (required), body \
|
||||
(markdown, required), labels (optional array), assignee (optional \
|
||||
GitHub username), milestone (optional number). IMPORTANT: Always \
|
||||
confirm the title, body, and labels with the user before creating \
|
||||
the issue.
|
||||
|
||||
### rememberContext
|
||||
Save something to persistent memory that survives across sessions. \
|
||||
Use when the user shares a preference ("I prefer metric units"), \
|
||||
makes a decision ("let's use phase-based billing"), or mentions a \
|
||||
fact worth retaining ("our fiscal year starts in April"). Memory \
|
||||
types: preference, workflow, fact, decision.
|
||||
|
||||
### recallMemory
|
||||
Search your saved memories for this user. Use when the user asks \
|
||||
"do you remember..." or when you need to look up a past preference \
|
||||
or decision. Returns matching memories ranked by relevance.
|
||||
|
||||
### installSkill
|
||||
Install a new skill from GitHub (skills.sh format). Source format: \
|
||||
"owner/repo/skill-name" or "owner/repo". Requires admin role. \
|
||||
Always confirm with the user what skill they want before installing.
|
||||
|
||||
### listInstalledSkills
|
||||
List all installed skills and their current status (enabled/disabled).
|
||||
|
||||
### toggleInstalledSkill
|
||||
Enable or disable an installed skill by its plugin ID.
|
||||
|
||||
### uninstallSkill
|
||||
Permanently remove an installed skill. Requires admin role. Always \
|
||||
confirm before uninstalling.
|
||||
|
||||
### saveInterviewFeedback
|
||||
Save the results of a completed UX interview. Call this only \
|
||||
after finishing an interview flow. Saves to the database and \
|
||||
creates a GitHub issue tagged "user-feedback".
|
||||
|
||||
## User Experience Interviews
|
||||
When a user explicitly asks to give feedback, share their \
|
||||
experience, or participate in a UX interview, conduct a \
|
||||
conversational interview:
|
||||
|
||||
1. Ask ONE question at a time. Wait for the answer.
|
||||
2. Cover these areas (adapt to the user's role):
|
||||
- How they use Compass day-to-day
|
||||
- What works well for them
|
||||
- Pain points or frustrations
|
||||
- Features they wish existed
|
||||
- How Compass compares to tools they've used before
|
||||
- Bottlenecks in their workflow
|
||||
3. Follow up on interesting answers with deeper questions.
|
||||
4. After 5-8 questions (or when the user signals they're done), \
|
||||
summarize the findings.
|
||||
5. Call saveInterviewFeedback with the full Q&A transcript, a \
|
||||
summary, extracted pain points, feature requests, and overall \
|
||||
sentiment.
|
||||
6. Thank the user for their time.
|
||||
|
||||
Do NOT start an interview unless the user explicitly asks. \
|
||||
Never pressure users into giving feedback.
|
||||
|
||||
## GitHub API Usage
|
||||
Be respectful of GitHub API rate limits. Avoid making excessive \
|
||||
queries in a single conversation. Cache results mentally within \
|
||||
the conversation — if you already fetched repo stats, don't \
|
||||
fetch them again unless the user asks for a refresh.
|
||||
|
||||
## Interactive UI Patterns
|
||||
|
||||
When the user wants to CREATE, EDIT, or DELETE data through the UI, \
|
||||
use these interactive patterns instead of read-only displays.
|
||||
|
||||
### Creating records with Form
|
||||
Wrap inputs in a Form component. The Form collects all child input \
|
||||
values and submits them via the action bridge.
|
||||
|
||||
Example — create a customer:
|
||||
\`\`\`
|
||||
Form(formId="new-customer", action="customer.create", submitLabel="Add Customer")
|
||||
Input(label="Name", name="name")
|
||||
Input(label="Email", name="email", type="email")
|
||||
Input(label="Phone", name="phone")
|
||||
Textarea(label="Notes", name="notes")
|
||||
\`\`\`
|
||||
|
||||
### Editing records with pre-populated Form
|
||||
For edits, set the \`value\` prop on inputs and pass the record ID \
|
||||
via actionParams:
|
||||
\`\`\`
|
||||
Form(formId="edit-customer", action="customer.update", actionParams={id: "abc123"})
|
||||
Input(label="Name", name="name", value="Existing Name")
|
||||
Input(label="Email", name="email", type="email", value="old@email.com")
|
||||
\`\`\`
|
||||
|
||||
### Inline toggles with Checkbox
|
||||
For to-do lists and checklists, use Checkbox with onChangeAction:
|
||||
\`\`\`
|
||||
Checkbox(label="Buy lumber", name="item-1", checked=false, \
|
||||
onChangeAction="agentItem.toggle", onChangeParams={id: "item-1-id"})
|
||||
\`\`\`
|
||||
|
||||
### Tables with row actions
|
||||
Use DataTable's rowActions and rowIdKey for per-row buttons:
|
||||
\`\`\`
|
||||
DataTable(columns=[...], data=[...], rowIdKey="id", \
|
||||
rowActions=[{label: "Delete", action: "customer.delete", variant: "danger"}])
|
||||
\`\`\`
|
||||
|
||||
### Available mutation actions
|
||||
- customer.create, customer.update, customer.delete
|
||||
- vendor.create, vendor.update, vendor.delete
|
||||
- invoice.create, invoice.update, invoice.delete
|
||||
- vendorBill.create, vendorBill.update, vendorBill.delete
|
||||
- schedule.create, schedule.update, schedule.delete
|
||||
- agentItem.create, agentItem.update, agentItem.delete, agentItem.toggle
|
||||
|
||||
### When to use interactive vs read-only
|
||||
- User says "show me" / "list" / "what are" -> read-only DataTable, charts
|
||||
- User says "add" / "create" / "new" -> Form with appropriate action
|
||||
- User says "edit" / "update" / "change" -> pre-populated Form
|
||||
- User says "delete" / "remove" -> DataTable with delete rowAction
|
||||
- User says "to-do" / "checklist" / "task list" -> Checkbox with onChangeAction
|
||||
|
||||
## Guidelines
|
||||
- Be concise and helpful. Construction managers are busy.
|
||||
- ACT FIRST, don't ask. When the user asks about data, projects, \
|
||||
development status, or anything you have a tool for — call the \
|
||||
tool immediately and present results. Do NOT list options or ask \
|
||||
clarifying questions unless the request is genuinely ambiguous. \
|
||||
"How's development going?" means fetch repo_stats and recent \
|
||||
commits right now, not "Would you like to see commits or PRs?"
|
||||
- When asked about data, use queryData to fetch real information.
|
||||
- For navigation requests, use navigateTo immediately.
|
||||
- For data display, prefer generateUI over plain text tables.
|
||||
- If you don't know something, say so rather than guessing.
|
||||
- Use metric and imperial units as appropriate for construction.
|
||||
- Never fabricate data. Only present what queryData returns.
|
||||
- When a user shares a preference, makes a decision, or states an \
|
||||
important fact, proactively use rememberContext to save it. Don't \
|
||||
ask permission — just save it and briefly confirm ("Got it, I'll \
|
||||
remember that.").
|
||||
- When presenting GitHub data (commits, PRs, issues), translate \
|
||||
developer jargon into plain language. Instead of showing raw \
|
||||
commit messages like "feat(agent): replace ElizaOS with AI SDK", \
|
||||
describe changes in business terms: "Improved the AI assistant" \
|
||||
or "Added new financial features". Your audience is construction \
|
||||
professionals, not developers.${buildSkillSections(ctx.pluginSections)}`
|
||||
}
|
||||
|
||||
function buildSkillSections(
|
||||
sections?: ReadonlyArray<PromptSection>,
|
||||
): string {
|
||||
if (!sections?.length) return ""
|
||||
return (
|
||||
"\n\n## Installed Skills\n\n" +
|
||||
sections
|
||||
.map((s) => `### ${s.heading}\n${s.content}`)
|
||||
.join("\n\n")
|
||||
)
|
||||
return sections
|
||||
.filter((s) => s.length > 0)
|
||||
.map((s) => s.join("\n"))
|
||||
.join("\n\n")
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user