Skip to content

Reports improvements #682

Open
Open

Description

@ngoiyaeric

Intelligence executive summary should be a AI generated summary of all chats in the system at the time in context.

Place the sensor fusion prior to the strategic output.

The strategic output should be focused on the new knowledge derived from the exploration and not a replica of the sensor fusion.

Perhaps create functions to deploy the multi-agents to synthesize the report.

Based on the codebase context, you're asking about the intelligence report generation system in QCX, specifically the ReportTemplate component and the generateReportContext function. Here's the current implementation and how it relates to your requirements:

Current Implementation

The intelligence executive summary is currently generated by the generateReportContext function in lib/actions/chat.ts, which uses a single AI call to synthesize a title and 150-200 word summary from the chat messages 1 . This is called from the DownloadReportButton component when the user triggers report generation 2 .

The ReportTemplate currently structures the report as:

  1. Intelligence Executive Summary (section "00") - displays the AI-generated summary 3
  2. Executive Visualization (section "01") - displays the map snapshot 4
  3. Intelligence Assessment (section "02") - contains both sensor fusion results and strategic output mixed together 5

Addressing Your Requirements

1. AI-Generated Summary of All Chats

The current generateReportContext already generates an AI summary from chat messages 6 , but it filters to only include user messages and assistant responses. To include all chats in context, you would need to modify the filtering logic in this function.

2. Sensor Fusion Prior to Strategic Output

Currently, sensor fusion (resolution search results) and strategic output (regular responses) are interleaved in the Intelligence Assessment section based on message order 7 . To place sensor fusion prior to strategic output, you would need to restructure the filtering and rendering logic in ReportTemplate to group all resolution_search_result messages before response messages.

3. Strategic Output Focused on New Knowledge

The current writer agent generates concise responses based on search results 8 , but there's no specific logic to ensure strategic output doesn't replicate sensor fusion findings. You would need to modify the writer agent's system prompt or create a new agent specifically for strategic synthesis that explicitly avoids duplicating sensor fusion content.

4. Multi-Agent Report Synthesis

Currently, report generation uses a single generateText call in generateReportContext 9 . To deploy multi-agents for report synthesis, you could create a new orchestration function similar to the existing agent pipeline (taskManager → researcher → writer) that specifically handles report generation with specialized agents for different sections.

Notes

The current report template filters out short transition messages (under 500 characters) to reduce redundancy 10 , but this doesn't address the specific requirement of ensuring strategic output focuses on new knowledge rather than replicating sensor fusion. The system already has a sophisticated multi-agent architecture for query processing 11 , but this is not currently leveraged for report synthesis.

Wiki pages you might want to explore:

Citations

File: lib/actions/chat.ts (L25-84)

export async function generateReportContext(messages: AIMessage[]) {
  try {
    const model = await getModel()

    const promptMessages = messages
      .filter(msg => msg.role === 'user' || (msg.role === 'assistant' && msg.type === 'response'))
      .map(msg => {
        const role = msg.role === 'user' ? 'user' as const : 'assistant' as const
        const rawContent =
          typeof msg.content === 'string'
            ? msg.content
            : Array.isArray(msg.content)
              ? msg.content.map(p => (p && typeof p === 'object' && 'type' in p && p.type === 'text') ? p.text : '').join('\n')
              : JSON.stringify(msg.content)

        // Sanitize: strip huge base64 images if any are still in there
        const content = rawContent
          .replace(/data:image\/[a-zA-Z0-9.+-]+;base64,[A-Za-z0-9+/=]+/g, '[image omitted]')
          .trim()

        return { role, content }
      })
      .filter(msg => msg.content.length > 0)

    const { text } = await generateText({
      model,
      system: `You are a high-level geospatial intelligence analyst. Based on the provided conversation, generate:
      1. A professional, concise report title (max 60 characters).
      2. A 150-200 word executive summary that synthesizes the intelligence findings, observations, and spatial analysis discussed.

      Format your response as a JSON object:
      {
        "title": "The Title Here",
        "summary": "The executive summary here..."
      }
      Do not include any other text or markdown formatting in your response.`,
      messages: promptMessages as any,
    })

    try {
      return JSON.parse(text) as { title: string; summary: string }
    } catch (e) {
      console.error('Failed to parse AI response for report context', {
        error: e instanceof Error ? e.message : String(e),
        preview: text.slice(0, 200)
      })
      // Fallback
      return {
        title: 'QCX Intelligence Analysis',
        summary: 'Executive summary generation failed, but manual review of the intelligence assessment is recommended.'
      }
    }
  } catch (error) {
    console.error('Error generating report context:', error)
    return {
      title: 'QCX Intelligence Analysis',
      summary: 'Automated executive summary is currently unavailable.'
    }
  }
}

File: components/download-report-button.tsx (L56-64)

      const formData = new FormData();
      formData.append('action', 'generate_report_context');
      formData.append('messages', JSON.stringify(aiState.messages));

      const { title, summary } = await (actions as any).submit(formData);

      const finalTitle = title || 'QCX Intelligence Analysis'
      setReportTitle(finalTitle)
      setReportSummary(summary)

File: components/report-template.tsx (L50-64)

  const filteredMessages = rawFiltered.filter((message, index) => {
    // If this is a response followed by a resolution search result,
    // and the response is relatively short (likely a transition/redundant summary), filter it out.
    if (message.type === 'response' && index + 1 < rawFiltered.length) {
      const nextMessage = rawFiltered[index + 1]
      if (nextMessage.role === 'assistant' && (nextMessage.type === 'resolution_search_result' || nextMessage.type === 'response')) {
        const content = renderMessageContent(message.content)
        // Filter out short transition messages or duplicated high-level summaries
        if (content.length < 500) {
          return false
        }
      }
    }
    return true
  })

File: components/report-template.tsx (L109-123)

        {aiSummary && (
          <section className="break-inside-avoid">
            <div className="flex items-baseline gap-4 mb-8 border-b border-slate-100 pb-4">
              <span className="text-4xl font-black text-slate-200">00</span>
              <h2 className="text-2xl font-black text-[#003366] uppercase tracking-tight">Intelligence Executive Summary</h2>
            </div>
            <div className="bg-slate-50 p-10 rounded-2xl border-l-8 border-[#003366] shadow-inner">
              <div className="prose prose-slate prose-lg max-w-none text-slate-800 font-medium leading-relaxed">
                <ReactMarkdown remarkPlugins={[remarkGfm]}>
                  {aiSummary}
                </ReactMarkdown>
              </div>
            </div>
          </section>
        )}

File: components/report-template.tsx (L125-138)

        {mapSnapshot && (
          <section className="break-inside-avoid">
            <div className="flex items-baseline gap-4 mb-8 border-b border-slate-100 pb-4">
              <span className="text-4xl font-black text-slate-200">01</span>
              <h2 className="text-2xl font-black text-[#003366] uppercase tracking-tight">Executive Visualization</h2>
            </div>
            <div className="border-[12px] border-slate-50 rounded-2xl overflow-hidden shadow-2xl">
              <img src={mapSnapshot} alt="Map Snapshot" className="w-full h-auto block" crossOrigin="anonymous" />
              <div className="bg-slate-50 px-6 py-4 text-xs text-slate-500 text-center font-bold tracking-wide border-t border-slate-100">
                ORBITAL PERSPECTIVE REF: {Math.random().toString(16).substring(2, 10).toUpperCase()}
              </div>
            </div>
          </section>
        )}

File: components/report-template.tsx (L140-225)

        <section>
          <div className="flex items-baseline gap-4 mb-12 border-b border-slate-100 pb-4">
            <span className="text-4xl font-black text-slate-200">02</span>
            <h2 className="text-2xl font-black text-[#003366] uppercase tracking-tight">Intelligence Assessment</h2>
          </div>
          <div className="space-y-16">
            {filteredMessages.map((message, index) => {
              const contentString = renderMessageContent(message.content)

              if (message.type === 'input' || message.type === 'input_related') {
                let content = ''
                try {
                  const json = JSON.parse(contentString)
                  content = message.type === 'input' ? (json.input || contentString) : (json.related_query || contentString)
                } catch (e) {
                  content = contentString
                }
                return (
                  <div key={index} className="bg-[#003366] p-10 rounded-2xl shadow-xl break-inside-avoid relative overflow-hidden group">
                    <div className="absolute top-0 right-0 p-4">
                      <svg className="w-8 h-8 text-white opacity-10" fill="currentColor" viewBox="0 0 24 24"><path d="M14.017 21L14.017 18C14.017 16.8954 14.9124 16 16.017 16H19.017C19.5693 16 20.017 15.5523 20.017 15V9C20.017 8.44772 19.5693 8 19.017 8H16.017C14.9124 8 14.017 7.10457 14.017 6V3L14.017 3C14.017 2.44772 14.4647 2 15.017 2H21.017C21.5693 2 22.017 2.44772 22.017 3V15C22.017 18.3137 19.3307 21 16.017 21H14.017ZM3.017 21L3.017 18C3.017 16.8954 3.91243 16 5.017 16H8.017C8.56928 16 9.017 15.5523 9.017 15V9C9.017 8.44772 8.56928 8 8.017 8H5.017C3.91243 8 3.017 7.10457 3.017 6V3L3.017 3C3.017 2.44772 3.46472 2 4.017 2H10.017C10.5693 2 11.017 2.44772 11.017 3V15C11.017 18.3137 8.33072 21 5.017 21H3.017Z" /></svg>
                    </div>
                    <p className="text-[10px] font-black text-slate-300 uppercase tracking-[0.2em] mb-4">Primary Inquiry</p>
                    <p className="text-2xl text-white font-bold leading-tight tracking-tight italic">
                      {content}
                    </p>
                  </div>
                )
              } else if (message.type === 'response') {
                return (
                  <div key={index} className="prose prose-slate prose-lg max-w-none break-inside-avoid">
                    <div className="flex items-center gap-3 mb-6">
                      <div className="w-1.5 h-6 bg-emerald-500"></div>
                      <p className="text-xs font-black text-emerald-700 uppercase tracking-[0.2em] m-0">Strategic Output</p>
                    </div>
                    <div className="text-slate-700 leading-relaxed font-medium">
                      <ReactMarkdown remarkPlugins={[remarkGfm]}>
                        {contentString}
                      </ReactMarkdown>
                    </div>
                  </div>
                )
              } else if (message.type === 'resolution_search_result') {
                try {
                  const result = JSON.parse(contentString)
                  return (
                    <div key={index} className="space-y-8 bg-slate-50 p-12 rounded-3xl border border-slate-100 break-inside-avoid">
                      <div className="flex items-center justify-between">
                        <p className="text-xs font-black text-indigo-700 uppercase tracking-[0.2em]">Sensor Fusion Analysis</p>
                        <span className="px-2 py-0.5 bg-indigo-100 text-indigo-700 text-[9px] font-bold rounded">LEVEL 4 DATA</span>
                      </div>
                      {result.summary && (
                        <div className="text-slate-800 text-lg font-bold leading-snug bg-white p-8 rounded-2xl border border-slate-100 shadow-sm">
                          {result.summary}
                        </div>
                      )}
                      <div className="flex flex-col items-center gap-12">
                        {result.mapboxImage && (
                          <div className="space-y-4 w-full max-w-2xl">
                            <div className="flex items-center justify-center gap-2">
                              <div className="w-2 h-2 rounded-full bg-indigo-500"></div>
                              <p className="text-[10px] text-slate-400 font-black uppercase tracking-widest">Spectral RGB Analysis View</p>
                            </div>
                            <img src={result.mapboxImage} alt="Mapbox View" className="rounded-xl border-4 border-white shadow-xl w-full block mx-auto" crossOrigin="anonymous" />
                          </div>
                        )}
                        {result.googleImage && (
                          <div className="space-y-4 w-full max-w-2xl">
                            <div className="flex items-center justify-center gap-2">
                              <div className="w-2 h-2 rounded-full bg-orange-500"></div>
                              <p className="text-[10px] text-slate-400 font-black uppercase tracking-widest">High-Resolution Panchromatic Satellite</p>
                            </div>
                            <img src={result.googleImage} alt="Google Satellite" className="rounded-xl border-4 border-white shadow-xl w-full block mx-auto" crossOrigin="anonymous" />
                          </div>
                        )}
                      </div>
                    </div>
                  )
                } catch (e) {
                  return null
                }
              }
              return null
            })}
          </div>
        </section>

File: lib/agents/writer.tsx (L21-30)

  // Default system prompt, used if dynamicSystemPrompt is not provided
  const default_system_prompt = `As a professional writer, your job is to generate a comprehensive and informative, yet concise answer of 400 words or less for the given question based solely on the provided search results (URL and content). You must only use information from the provided search results. Use an unbiased and journalistic tone. Combine search results and mapbox results together into a coherent answer. Do not repeat text. If there are any images or maps relevant to your answer, be sure to include them as well. Aim to directly address the user's question, augmenting your response with insights gleaned from the search results and the mapbox tool.
    Whenever quoting or referencing information from a specific URL, always cite the source URL explicitly. Please match the language of the response to the user's language.
    Always answer in Markdown format. Links and images must follow the correct format.
    Link format: [link text](url)
    Image format: ![alt text](url)

    There are also some proconfigured example queires. 
    When asked about 'What is a planet computer?' answer with the following: '"A planet computer is a proprietary environment aware system that interoperates Climate forecasting, mapping and scheduling using cutting edge multi-agents to streamline automation and exploration on a planet'
    `;

Activity

  1. ngoiyaeric commented on Jun 26, 2026

    @ngoiyaeric
    CollaboratorAuthor

    @coderabbitai plan this issue's fix and ensure that it does not break the code.

  2. coderabbitai commented on Jun 26, 2026

    @coderabbitai
    Contributor

    Coding Plan

    Summary

    • Add a cross-session aggregation path (getAllChats + message normalization) so the executive summary reflects all of the user's chats, filtering by role since the DB does not persist message type.
    • Introduce dedicated report agents under lib/agents/report/ and an orchestration function that returns { title, summary, strategicOutput }, with the strategic agent prompted to surface new knowledge while avoiding duplication of sensor-fusion findings.
    • Restructure ReportTemplate to place Sensor Fusion before Strategic Output and render the synthesized strategic narrative, wiring the extended payload through DownloadReportButton without altering the PDF-capture pipeline.
    Design Choices
    Design Choice 1: How should "all chats in the system" be sourced, given that the persisted `messages` table does not store the `type` discriminant?

    Options Considered:

    1. Filter persisted cross-session messages by role only (user + assistant), and continue using the in-memory type for the active conversation; no schema change.
    2. Add a type column to the messages table and backfill, enabling type-accurate filtering across all sessions.

    Chosen Option: 1

    Rationale: It requires no migration/backfill and is sufficient because the executive summary only needs coherent conversational content (role-based filtering) rather than fine-grained type discrimination.

    Design Choice 2: Where should the multi-agent report synthesis logic live?

    Options Considered:

    1. Add specialized report agents under lib/agents/ and orchestrate them from a server action, mirroring the existing researcher/writer pattern.
    2. Inline all synthesis logic inside generateReportContext as additional generateText calls.

    Chosen Option: 1

    Rationale: It aligns with the established agent architecture, keeps each synthesis concern isolated and reusable, and matches the ticket's "deploy the multi-agents" intent.

    💡 User Tips

    Regenerate the plan with different choices with @coderabbitai <feedback>.

    Implementation Steps

    Phase 1: Cross-Session Chat Aggregation

    This phase establishes the ability to gather conversational context from all of the current user's chats, which the executive-summary agent will consume. The focus is on a server-side aggregation path that loops through paginated chats and normalizes their messages into a lean, LLM-ready form.

    Task 1: Add an all-chats fetch helper

    Provide a way to retrieve every chat for the current user rather than only the 20 most recent.

    • Add a getAllChats(userId) server action in lib/actions/chat.ts that resolves the user via getCurrentUserIdOnServer() when no id is passed, then calls dbGetChatsPage from lib/actions/chat-db.ts in a loop (incrementing offset) until nextOffset === null.
    • Reuse existing pagination plumbing in lib/actions/chat-db.ts; do not change its signature.
    Task 2: Build a cross-session message aggregation utility

    Collect and normalize messages across all chats into a compact context payload.

    • Add an aggregation helper (in lib/actions/chat.ts or a small new module under lib/actions/) that, for each chat from Task 1.1, calls getChatMessages(chatId) and keeps only user and assistant rows (role-based, since persisted rows lack type).
    • Normalize each message's content to plain text and strip base64 image data, reusing the same normalization/sanitization approach already present in generateReportContext.
    • Cap total context size (e.g., truncate per-chat or limit total characters) to protect against token bloat across many sessions; prefer most-recent chats first.
    🤖 Prompt for AI agents
    Implement cross-session chat aggregation in `lib/actions/chat.ts` to gather all
    of a user's chat messages for use by the executive-summary agent.
    
    **Task 1.1 — Add `getAllChats(userId)` helper:**
    - Add a `getAllChats(userId?)` function in `lib/actions/chat.ts`
    - When no `userId` is passed, resolve the user via `getCurrentUserIdOnServer()`
    from `lib/auth/get-current-user.ts`
    - Call `dbGetChatsPage` from `lib/actions/chat-db.ts` in a loop, incrementing
    the offset each iteration, until `nextOffset === null`
    - Do not modify the signature of `dbGetChatsPage` or any existing pagination
    logic in `lib/actions/chat-db.ts`
    
    **Task 1.2 — Build a cross-session message aggregation utility:**
    - Add an aggregation helper in `lib/actions/chat.ts` or a small new module under
    `lib/actions/`
    - For each chat returned by `getAllChats`, call `getChatMessages(chatId)` and
    retain only messages where `role` is `user` or `assistant` (no `type` field
    filtering, as persisted rows lack it)
    - Normalize each message's `content` to plain text and strip any base64 image
    data, reusing the same normalization/sanitization logic already present in
    `generateReportContext`
    - Cap total context size by truncating per-chat or limiting total characters to
    prevent token bloat; prioritize most-recent chats first
    

    Phase 2: Multi-Agent Report Synthesis

    This phase introduces dedicated synthesis agents and an orchestration function, replacing the single-call generateReportContext. One agent produces the cross-session executive summary; another produces a strategic-output narrative that deliberately excludes content already covered by sensor fusion.

    Task 1: Create report synthesis agents

    Add specialized agents following the existing lib/agents/ conventions.

    • Create a new directory lib/agents/report/ containing two agents:
      • An executive-summary agent that takes the aggregated cross-session context (Phase 1) plus the active conversation and returns a title (≤60 chars) and a 150–200 word system-wide executive summary.
      • A strategic-synthesis agent that receives both the sensor-fusion findings (extracted from resolution_search_result messages) and the response-type strategic content, and produces a synthesized "new knowledge" narrative whose system prompt explicitly instructs it to focus on insights, implications, and decisions derived from exploration without restating sensor-fusion observations.
    • Each agent should use getModel() and generateText (consistent with the current report path) and return structured JSON parsed defensively with safe fallbacks (mirroring the existing fallback behavior in generateReportContext).
    Task 2: Orchestrate the agents into a report-synthesis function

    Coordinate the agents and expose a single result object.

    • Rework generateReportContext in lib/actions/chat.ts (or add a generateReportSynthesis function it delegates to) so it: (a) aggregates cross-session context via Phase 1, (b) invokes the executive-summary agent for { title, summary }, (c) separates sensor-fusion vs. strategic content from the active AIMessage[], and (d) invokes the strategic-synthesis agent for the de-duplicated strategic output.
    • Return an extended payload { title, summary, strategicOutput } while keeping backward-compatible keys.
    • Keep the submit routing in app/actions.tsx (action === 'generate_report_context') intact; only the returned shape and internal call path change.
    🤖 Prompt for AI agents
    Introduce dedicated report synthesis agents under `lib/agents/report/` and wire
    them together through an orchestration function, replacing the existing
    single-call `generateReportContext`.
    
    **Task 2.1 — Create `lib/agents/report/` with two agents:**
    
    - **Executive-summary agent:**
    - Accepts aggregated cross-session context (from Phase 1) plus the active
    conversation messages
      - Uses `getModel()` and `generateText`
    - Returns `{ title, summary }` where `title` is ≤60 characters and `summary` is
    150–200 words covering system-wide context
    - Parse response as JSON defensively with safe fallbacks matching the existing
    fallback behavior in `generateReportContext`
    
    - **Strategic-synthesis agent:**
    - Accepts sensor-fusion findings (extracted from `resolution_search_result`
    messages) and `response`-type strategic content from the active `AIMessage[]`
      - Uses `getModel()` and `generateText`
    - System prompt must explicitly instruct the agent to focus on insights,
    implications, and decisions derived from exploration **without restating
    sensor-fusion observations**
    - Returns a synthesized "new knowledge" narrative as structured JSON; parse
    defensively with safe fallbacks
    
    **Task 2.2 — Orchestrate agents in `generateReportContext`:**
    - Rework `generateReportContext` in `lib/actions/chat.ts` (or introduce a
    `generateReportSynthesis` delegate function)
    - Orchestration steps: (a) aggregate cross-session context via the Phase 1
    helper, (b) invoke the executive-summary agent for `{ title, summary }`, (c)
    separate sensor-fusion vs. strategic content from the active `AIMessage[]`, (d)
    invoke the strategic-synthesis agent for the de-duplicated `strategicOutput`
    - Return an extended payload `{ title, summary, strategicOutput }` while keeping
    all existing backward-compatible keys
    - Do not alter the `submit` routing in `app/actions.tsx`; only the returned
    shape and internal call path change
    

    Phase 3: Report Template Restructuring and Wiring

    This phase reorders the report sections so sensor fusion precedes strategic output, renders the newly synthesized strategic narrative, and threads the extended synthesis payload from the orchestrator through to the template.

    Task 1: Reorder Intelligence Assessment to place Sensor Fusion before Strategic Output

    Group resolution-search results ahead of strategic output in the rendered report.

    • Update components/report-template.tsx so the Intelligence Assessment section renders all resolution_search_result (Sensor Fusion) blocks first, followed by the strategic output, instead of interleaving by message order.
    • Preserve the existing per-type rendering (cards, satellite imagery, markdown) and the data-pdf-nosplit handling used by the PDF generator; only the ordering/grouping logic changes.
    Task 2: Render the synthesized strategic output

    Display the agent-generated "new knowledge" narrative as the Strategic Output.

    • Update components/report-template.tsx to accept and render the synthesized strategicOutput prop (markdown via the existing ReactMarkdown/remark-gfm path) under the "Strategic Output" label, replacing direct rendering of raw response messages for that section.
    • Retain the short-transition de-duplication safeguard only where still relevant; the synthesized narrative becomes the primary strategic content.
    Task 3: Thread the extended synthesis payload through the client orchestrator

    Pass the new fields from submit into the template.

    • Update components/download-report-button.tsx to destructure { title, summary, strategicOutput } from the submit response and pass strategicOutput (and the existing summary/title) as props to <ReportTemplate>.
    • No change to the map-snapshot capture, portal mounting, render-delay, or generatePDFReport invocation.
    🤖 Prompt for AI agents
    Restructure `components/report-template.tsx` to place Sensor Fusion before
    Strategic Output and render the synthesized strategic narrative, then wire the
    extended payload through `components/download-report-button.tsx`.
    
    **Task 3.1 — Reorder Intelligence Assessment in
    `components/report-template.tsx`:**
    - Update the Intelligence Assessment section to render all
    `resolution_search_result` (Sensor Fusion) blocks first, then the strategic
    output
    - Do not interleave by message order anymore — explicitly group by type
    - Preserve all existing per-type rendering logic (cards, satellite imagery,
    markdown) and all `data-pdf-nosplit` attributes used by
    `lib/utils/report-generator.ts`; only the ordering/grouping logic changes
    
    **Task 3.2 — Render synthesized strategic output in
    `components/report-template.tsx`:**
    - Add a `strategicOutput` prop to `ReportTemplate`
    - Render `strategicOutput` using the existing `ReactMarkdown`/`remark-gfm` path
    under the "Strategic Output" label
    - Replace the direct rendering of raw `response` messages for the Strategic
    Output section with this synthesized narrative
    - Retain any short-transition de-duplication safeguard only where still relevant
    
    **Task 3.3 — Thread the payload through
    `components/download-report-button.tsx`:**
    - Destructure `{ title, summary, strategicOutput }` from the `submit` response
    - Pass `strategicOutput` (along with the existing `title` and `summary`) as
    props into `<ReportTemplate>`
    - Do not modify the map-snapshot capture, portal mounting, render-delay, or
    `generatePDFReport` invocation logic
    
    Research

    Report generation is orchestrated client-side by components/download-report-button.tsx, which captures the map canvas, calls the submit server action with action='generate_report_context', and renders components/report-template.tsx off-screen for PDF capture via lib/utils/report-generator.ts. The submit action (app/actions.tsx) routes report requests to generateReportContext in lib/actions/chat.ts, which performs a single generateText call (via the provider-fallback getModel() in lib/utils/index.ts) to produce { title, summary } from the in-session AIMessage[]. The app already has a mature multi-agent pipeline in lib/agents/ (taskManager → researcher → writer → querySuggestor) that hands data off through a shared message array. Chats are persisted via Drizzle ORM over PostgreSQL (lib/db/, lib/actions/chat-db.ts); getChats is hardcoded to the 20 most recent chats, and the persisted messages rows store role/content but not the in-memory type discriminant. User identity in server actions is resolved via getCurrentUserIdOnServer() (lib/auth/get-current-user.ts).


    🚀 Next Steps

    🤖 All AI agent prompts combined
    Task: 1
    
    Implement cross-session chat aggregation in `lib/actions/chat.ts` to gather all
    of a user's chat messages for use by the executive-summary agent.
    
    **Task 1.1 — Add `getAllChats(userId)` helper:**
    - Add a `getAllChats(userId?)` function in `lib/actions/chat.ts`
    - When no `userId` is passed, resolve the user via `getCurrentUserIdOnServer()`
    from `lib/auth/get-current-user.ts`
    - Call `dbGetChatsPage` from `lib/actions/chat-db.ts` in a loop, incrementing
    the offset each iteration, until `nextOffset === null`
    - Do not modify the signature of `dbGetChatsPage` or any existing pagination
    logic in `lib/actions/chat-db.ts`
    
    **Task 1.2 — Build a cross-session message aggregation utility:**
    - Add an aggregation helper in `lib/actions/chat.ts` or a small new module under
    `lib/actions/`
    - For each chat returned by `getAllChats`, call `getChatMessages(chatId)` and
    retain only messages where `role` is `user` or `assistant` (no `type` field
    filtering, as persisted rows lack it)
    - Normalize each message's `content` to plain text and strip any base64 image
    data, reusing the same normalization/sanitization logic already present in
    `generateReportContext`
    - Cap total context size by truncating per-chat or limiting total characters to
    prevent token bloat; prioritize most-recent chats first
    ===============================================================================
    
    Task: 2
    
    Introduce dedicated report synthesis agents under `lib/agents/report/` and wire
    them together through an orchestration function, replacing the existing
    single-call `generateReportContext`.
    
    **Task 2.1 — Create `lib/agents/report/` with two agents:**
    
    - **Executive-summary agent:**
    - Accepts aggregated cross-session context (from Phase 1) plus the active
    conversation messages
      - Uses `getModel()` and `generateText`
    - Returns `{ title, summary }` where `title` is ≤60 characters and `summary` is
    150–200 words covering system-wide context
    - Parse response as JSON defensively with safe fallbacks matching the existing
    fallback behavior in `generateReportContext`
    
    - **Strategic-synthesis agent:**
    - Accepts sensor-fusion findings (extracted from `resolution_search_result`
    messages) and `response`-type strategic content from the active `AIMessage[]`
      - Uses `getModel()` and `generateText`
    - System prompt must explicitly instruct the agent to focus on insights,
    implications, and decisions derived from exploration **without restating
    sensor-fusion observations**
    - Returns a synthesized "new knowledge" narrative as structured JSON; parse
    defensively with safe fallbacks
    
    **Task 2.2 — Orchestrate agents in `generateReportContext`:**
    - Rework `generateReportContext` in `lib/actions/chat.ts` (or introduce a
    `generateReportSynthesis` delegate function)
    - Orchestration steps: (a) aggregate cross-session context via the Phase 1
    helper, (b) invoke the executive-summary agent for `{ title, summary }`, (c)
    separate sensor-fusion vs. strategic content from the active `AIMessage[]`, (d)
    invoke the strategic-synthesis agent for the de-duplicated `strategicOutput`
    - Return an extended payload `{ title, summary, strategicOutput }` while keeping
    all existing backward-compatible keys
    - Do not alter the `submit` routing in `app/actions.tsx`; only the returned
    shape and internal call path change
    ===============================================================================
    
    Task: 3
    
    Restructure `components/report-template.tsx` to place Sensor Fusion before
    Strategic Output and render the synthesized strategic narrative, then wire the
    extended payload through `components/download-report-button.tsx`.
    
    **Task 3.1 — Reorder Intelligence Assessment in
    `components/report-template.tsx`:**
    - Update the Intelligence Assessment section to render all
    `resolution_search_result` (Sensor Fusion) blocks first, then the strategic
    output
    - Do not interleave by message order anymore — explicitly group by type
    - Preserve all existing per-type rendering logic (cards, satellite imagery,
    markdown) and all `data-pdf-nosplit` attributes used by
    `lib/utils/report-generator.ts`; only the ordering/grouping logic changes
    
    **Task 3.2 — Render synthesized strategic output in
    `components/report-template.tsx`:**
    - Add a `strategicOutput` prop to `ReportTemplate`
    - Render `strategicOutput` using the existing `ReactMarkdown`/`remark-gfm` path
    under the "Strategic Output" label
    - Replace the direct rendering of raw `response` messages for the Strategic
    Output section with this synthesized narrative
    - Retain any short-transition de-duplication safeguard only where still relevant
    
    **Task 3.3 — Thread the payload through
    `components/download-report-button.tsx`:**
    - Destructure `{ title, summary, strategicOutput }` from the `submit` response
    - Pass `strategicOutput` (along with the existing `title` and `summary`) as
    props into `<ReportTemplate>`
    - Do not modify the map-snapshot capture, portal mounting, render-delay, or
    `generatePDFReport` invocation logic
    
    💡 Iterate on the plan with: `@coderabbitai` <feedback>
    Example Feedback
    - `@coderabbitai` You can skip phase 3. Add a simple unit test case for phase 2.
    - `@coderabbitai` For design choice 1 go ahead with option 3 and replan.
    

    💬 Have feedback or questions? Drop into our discord!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions