tracer-sh 0.2.5 → 0.2.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tracer-sh",
3
- "version": "0.2.5",
3
+ "version": "0.2.7",
4
4
  "type": "module",
5
5
  "description": "Local-first debugging & analysis platform",
6
6
  "license": "SEE LICENSE IN LICENSE",
@@ -16548,6 +16548,49 @@ var TOOL_NAMES = {
16548
16548
  var CLIENT_TOOL_NAMES = Object.fromEntries(
16549
16549
  Object.entries(TOOL_NAMES).map(([k, v]) => [k, `tool-${v}`])
16550
16550
  );
16551
+ var ANALYSIS_MARKER = "<analysis>";
16552
+ function isAnalysisMessage(msg) {
16553
+ if (msg.role !== "assistant" || !msg.parts) return false;
16554
+ return msg.parts.some((p) => {
16555
+ if (p.type === CLIENT_TOOL_NAMES.BEGIN_ANALYSIS) return true;
16556
+ if (p.type !== "text") return false;
16557
+ const text3 = p.text;
16558
+ return typeof text3 === "string" && text3.includes(ANALYSIS_MARKER);
16559
+ });
16560
+ }
16561
+ function compactionUpTo(messages, boundaryIdx) {
16562
+ const boundary = messages[boundaryIdx];
16563
+ if (!boundary || boundary.role !== "assistant") return null;
16564
+ if (!isAnalysisMessage(boundary)) return boundaryIdx + 1;
16565
+ return boundaryIdx >= 1 ? boundaryIdx : null;
16566
+ }
16567
+ function findAnalysisMarker(parts) {
16568
+ const toolIdx = parts.findIndex((p) => p.type === CLIENT_TOOL_NAMES.BEGIN_ANALYSIS);
16569
+ if (toolIdx !== -1) return { kind: "tool", partIdx: toolIdx };
16570
+ for (let i = 0; i < parts.length; i++) {
16571
+ const p = parts[i];
16572
+ if (p.type !== "text") continue;
16573
+ const text3 = p.text;
16574
+ if (typeof text3 !== "string") continue;
16575
+ const idx = text3.indexOf(ANALYSIS_MARKER);
16576
+ if (idx !== -1) return { kind: "text", partIdx: i, charIdx: idx };
16577
+ }
16578
+ return null;
16579
+ }
16580
+ function splitAtAnalysis(parts) {
16581
+ const marker26 = findAnalysisMarker(parts);
16582
+ if (!marker26) return null;
16583
+ if (marker26.kind === "tool") {
16584
+ return { before: parts.slice(0, marker26.partIdx), analysis: parts.slice(marker26.partIdx + 1) };
16585
+ }
16586
+ const p = parts[marker26.partIdx];
16587
+ const beforeText = p.text.slice(0, marker26.charIdx);
16588
+ const afterText = p.text.slice(marker26.charIdx + ANALYSIS_MARKER.length);
16589
+ return {
16590
+ before: [...parts.slice(0, marker26.partIdx), ...beforeText.trim() ? [{ ...p, text: beforeText }] : []],
16591
+ analysis: [...afterText.trim() ? [{ ...p, text: afterText }] : [], ...parts.slice(marker26.partIdx + 1)]
16592
+ };
16593
+ }
16551
16594
  var ImportedAnalysisSchema = external_exports.object({
16552
16595
  v: external_exports.literal(1),
16553
16596
  kind: external_exports.literal("analysis"),
@@ -20371,6 +20414,13 @@ var chatSessions = sqliteTable("chat_sessions", {
20371
20414
  status: text("status").notNull().default("idle"),
20372
20415
  /** Null for normal sessions. "imported" for sessions re-hydrated from a dropped analysis PNG. */
20373
20416
  kind: text("kind"),
20417
+ /** Compaction: LLM-generated (possibly user-edited) summary of the first summaryUpTo messages. */
20418
+ summary: text("summary"),
20419
+ /** Count of leading messages covered by the summary. Index-based because
20420
+ * assistant messages can carry empty ids; prefixes are stable here (messages
20421
+ * are only appended or suffix-truncated, never reordered). */
20422
+ summaryUpTo: integer2("summary_up_to"),
20423
+ summaryCreatedAt: integer2("summary_created_at"),
20374
20424
  createdAt: integer2("created_at").notNull().$defaultFn(() => unixNow()),
20375
20425
  updatedAt: integer2("updated_at").notNull().$defaultFn(() => unixNow())
20376
20426
  }, (t2) => [
@@ -20514,6 +20564,9 @@ function runSetup() {
20514
20564
  messages TEXT NOT NULL,
20515
20565
  status TEXT NOT NULL DEFAULT 'idle',
20516
20566
  kind TEXT,
20567
+ summary TEXT,
20568
+ summary_up_to INTEGER,
20569
+ summary_created_at INTEGER,
20517
20570
  created_at INTEGER NOT NULL DEFAULT (unixepoch()),
20518
20571
  updated_at INTEGER NOT NULL DEFAULT (unixepoch())
20519
20572
  );
@@ -20617,7 +20670,12 @@ function runSetup() {
20617
20670
  for (const ddl of [
20618
20671
  `ALTER TABLE sub_agent_runs ADD COLUMN session_id TEXT`,
20619
20672
  `ALTER TABLE tool_memories ADD COLUMN review_note TEXT`,
20620
- `ALTER TABLE chat_sessions ADD COLUMN kind TEXT`
20673
+ `ALTER TABLE chat_sessions ADD COLUMN kind TEXT`,
20674
+ `ALTER TABLE chat_sessions ADD COLUMN summary TEXT`,
20675
+ `ALTER TABLE chat_sessions ADD COLUMN summary_up_to INTEGER`,
20676
+ `ALTER TABLE chat_sessions ADD COLUMN summary_created_at INTEGER`,
20677
+ // Drops the short-lived id-based boundary column (never shipped in a release).
20678
+ `ALTER TABLE chat_sessions DROP COLUMN summary_up_to_id`
20621
20679
  ]) {
20622
20680
  try {
20623
20681
  sqlite.exec(ddl);
@@ -37940,7 +37998,7 @@ function injectMemories(prompt, memoryContext) {
37940
37998
  const memoryBlock = `
37941
37999
 
37942
38000
  ## ${MEMORY_SECTION_NAME}
37943
- These OVERRIDE any conflicting instructions above \u2014 they are verified fixes from past errors:
38001
+ These OVERRIDE any conflicting instructions above \u2014 they are lessons recorded from real failures in past sessions:
37944
38002
  ${lines.join("\n")}
37945
38003
  `;
37946
38004
  const firstBreak = prompt.indexOf("\n\n");
@@ -46233,6 +46291,8 @@ You MUST save a memory for every query failure:
46233
46291
  - Wrong field names \u2192 "Don't use X, use Y for [purpose]"
46234
46292
  - Wrong event types \u2192 "Don't query X for [goal], use Y instead"
46235
46293
 
46294
+ **Corrections must be demonstrated, not guessed.** Only write "use Y instead" if Y was actually run in this session and succeeded. If no working alternative was demonstrated, save only the mistake ("Don't use X in [context]") \u2014 a wrong correction is worse than none, because memories override future instructions.
46295
+
46236
46296
  ### From struggle patterns (evaluated)
46237
46297
  Review the full session timeline for patterns where the agent struggled \u2014 multiple attempts with variations before finding the correct approach. Look for:
46238
46298
  - Repeated EMPTY results with name/field/value variations followed by eventual success
@@ -46591,7 +46651,7 @@ function sanitizeNrqlRows(rows) {
46591
46651
  }
46592
46652
 
46593
46653
  // src/lib/shared-prompts.ts
46594
- var UNIFIED_ROLE_INTRO = `You are Tracer, an observability expert in a direct conversation with a developer. You have DIRECT access to the query tools of multiple providers at once \u2014 each provider's syntax, fields, and debugging guidance are documented below. Pick the right provider(s) for each question; when a question spans providers, query them and correlate across the results in one investigation. You have full conversation history and can reference previous messages. You run as an AUTONOMOUS MULTI-STEP AGENT \u2014 after each tool call you automatically receive results and CAN (and often SHOULD) make additional tool calls before finishing.`;
46654
+ var UNIFIED_ROLE_INTRO = `You are Tracer, an observability expert in a direct conversation with a developer. You have DIRECT access to the query tools of multiple providers at once \u2014 each provider's syntax, common fields, and debugging guidance are documented below. Pick the right provider(s) for each question; when a question spans providers, query them and correlate across the results in one investigation. You have full conversation history and can reference previous messages. You run as an AUTONOMOUS MULTI-STEP AGENT \u2014 after each tool call you automatically receive results and CAN (and often SHOULD) make additional tool calls before finishing.`;
46595
46655
  function buildUnifiedModePrompt(providerFragments, maxSteps) {
46596
46656
  return `${UNIFIED_ROLE_INTRO}
46597
46657
 
@@ -46600,6 +46660,8 @@ ${buildRules({ investigation: true })}
46600
46660
 
46601
46661
  ${DETECTIVE_MINDSET}
46602
46662
 
46663
+ ${EVIDENCE_GROUNDING}
46664
+
46603
46665
  ${EXECUTION_DISCIPLINE}
46604
46666
 
46605
46667
  ${providerFragments.join("\n\n---\n\n")}
@@ -46641,13 +46703,20 @@ You have limited steps. Every query must earn its place. Your goal is the **fast
46641
46703
  3. **"Is there a single query that could answer multiple questions at once?"** \u2014 Combine work. Pack information density per query.
46642
46704
 
46643
46705
  **"Good enough" beats "complete."** The user can always ask follow-up questions. Don't anticipate them \u2014 answer what was asked.`;
46706
+ var EVIDENCE_GROUNDING = `## Grounded in Evidence
46707
+
46708
+ Your only sources of truth are the literal text of tool results from this session, what the user has stated in the conversation, and what this prompt documents. Anything else is unknown \u2014 including the meaning of the data you retrieve.
46709
+
46710
+ 1. **Field names and values are opaque labels.** Never translate or assign meaning to a field name, enum value, code, or flag beyond its literal text \u2014 systems attach internal meanings you cannot know. Report the raw value; if its meaning matters and is undocumented, say so.
46711
+ 2. **Absence requires an empty probe.** Only claim something is missing, absent, or "not on file" if a query that would have returned it came back empty. Not having looked is not evidence of absence.
46712
+ 3. **Separate facts, deductions, and gaps.** Facts restate query results. Deductions must follow from stated facts alone \u2014 present them as deductions and name the supporting results; correlation across results is not causation. Gaps are reported as "the data does not show X" \u2014 never filled with a plausible story.`;
46644
46713
  var NO_FIXES_RULE = `**NEVER suggest fixes, remediation, next steps, or actions.** Forbidden phrasings include: "consider," "you should," "try," "might want to," "recommend," "could help," "suggests [action]," "would resolve," "to fix this." Any sentence about what to DO about the problem is forbidden, regardless of phrasing. Your job ends at "here is what happened and the evidence." The developer decides what to do.`;
46645
46714
  var EXECUTION_DISCIPLINE = `## Execution Discipline
46646
46715
 
46647
46716
  For multi-step investigations:
46648
46717
  1. **Step N: [Goal]** \u2014 state what gap this fills
46649
46718
  2. **Tool call** \u2192 ONE query
46650
- 3. **\u2192 Found:** [data] **\u2192 So what:** [inference]
46719
+ 3. **\u2192 Found:** [data] **\u2192 So what:** [only what this data supports \u2014 if it needs an assumption, it's a gap, not a finding]
46651
46720
  4. **\u2192 Can I answer now?** \u2014 If YES: respond. If NO: state what's missing.
46652
46721
 
46653
46722
  For simple questions (counts, lookups), skip this \u2014 just answer directly.`;
@@ -46661,6 +46730,7 @@ function analysisBlock() {
46661
46730
 
46662
46731
  1. **Think first** \u2014 before writing anything, plan the evidence chain in your head:
46663
46732
  - Known facts from query results, inferences that follow from them, and remaining gaps.
46733
+ - Self-audit each claim against the tool results already in this session: if no specific result backs it, drop it or present it explicitly as unverified. This is an in-head check \u2014 never run extra investigation queries for it.
46664
46734
  - Which queries best VISUALIZE each finding \u2014 these become the tool calls you will run in this section.
46665
46735
  - Do not start writing until you have a clear chain and a concrete list of visuals to run.
46666
46736
  2. ${markerStep}
@@ -46686,7 +46756,7 @@ You have a maximum of ${maxSteps} steps. Most investigations should finish in 3-
46686
46756
 
46687
46757
  ## Final Reminders
46688
46758
  - **Tool calls are the evidence.** Every substantive claim in your response needs a visual \u2014 even if the same query already ran during investigation, re-run it here. The analysis section must be self-contained.
46689
- - **Follow the Detective mindset:** correlation \u2260 causation, no gap-filling, no fixes. Every claim traces to a specific query result. Say "insufficient data" when data is missing.`;
46759
+ - **Stay Grounded in Evidence:** every claim maps to a specific tool result; values mean only what their literal text says; absence claims need an empty probe; gaps are stated as "the data does not show". No fixes.`;
46690
46760
  }
46691
46761
 
46692
46762
  // src/lib/prompt-builder.ts
@@ -46715,6 +46785,8 @@ ${buildRules({ investigation: true, extraRules: config2.extraRules })}
46715
46785
 
46716
46786
  ${DETECTIVE_MINDSET}
46717
46787
 
46788
+ ${EVIDENCE_GROUNDING}
46789
+
46718
46790
  ${config2.insideOutDebugging}
46719
46791
 
46720
46792
  ${EXECUTION_DISCIPLINE}
@@ -56769,6 +56841,137 @@ var memoryRouter = router({
56769
56841
  })
56770
56842
  });
56771
56843
 
56844
+ // src/agents/utility/summary.ts
56845
+ var SUMMARY_SYSTEM_PROMPT = `You are compacting an AI debugging-assistant conversation into a detailed summary. Your summary will permanently REPLACE the original messages as the assistant's only memory of them, so anything you omit is lost forever. The assistant must be able to continue the investigation from your summary alone without redoing any completed work.
56846
+
56847
+ Write the summary as markdown with exactly these sections:
56848
+
56849
+ ## Original intent
56850
+ What the user set out to do, in their own framing. Include later refinements or pivots of the goal.
56851
+
56852
+ ## User requests & decisions
56853
+ Chronological list of every instruction, question, constraint, correction, and approval or rejection the user gave, and whether each was fulfilled. The assistant must be able to tell from this section alone what the user has and has not asked for.
56854
+
56855
+ ## Investigation log
56856
+ Numbered, chronological. Each entry pairs an action with its result:
56857
+ 1. One line on what was done and why, then the exact tool call, query, or API call (with its parameters and the time range it covered) in a fenced code block.
56858
+
56859
+ \u2192 Result: what it returned, with the key values verbatim. Render a result with more than one row (facets, top-N lists, table rows) as a markdown table; copy up to roughly 20 rows verbatim and note what was cut.
56860
+
56861
+ Never record an action without its result \u2014 an unpaired action forces the assistant to re-run it.
56862
+
56863
+ ## Key findings & data (verbatim)
56864
+ The distilled facts the investigation established, with exact values copied verbatim \u2014 never paraphrase these:
56865
+ - IDs of any kind (trace IDs, entity GUIDs, account/project IDs, session IDs)
56866
+ - Exact error messages and stack-trace lines
56867
+ - File paths, service names, host names, URLs
56868
+ - Numbers: counts, rates, percentages, latencies, timestamps, time ranges
56869
+ Do not re-copy queries or result tables that already appear in the Investigation log \u2014 state the facts they established and name the log entry they came from.
56870
+
56871
+ ## What did NOT work (dead ends)
56872
+ Approaches tried and abandoned, queries that errored or returned empty, hypotheses ruled out \u2014 and WHY each failed. This prevents the assistant from repeating them. If nothing failed, write "None."
56873
+
56874
+ ## Conclusions & current state
56875
+ Each conclusion the assistant reached, stated together with the evidence supporting it, so it is never re-derived. What was communicated or delivered to the user (answers, recommendations, reports), and any artifacts produced.
56876
+
56877
+ ## Open items
56878
+ Unresolved questions, pending next steps, anything the user asked for that has not been delivered yet. If none, write "None."
56879
+
56880
+ Rules:
56881
+ - Be detailed. Length is not a concern; losing information is. A long, precise summary is always better than a short, vague one.
56882
+ - Copy identifiers, queries, errors, and numbers character-for-character from the conversation.
56883
+ - Format for scanning: queries and commands go in fenced code blocks, multi-row results in markdown tables, and inline identifiers (service names, error classes, IDs, paths) in backticks.
56884
+ - State each piece of data in full exactly once, in the section where it belongs; later mentions reference it instead of repeating it.
56885
+ - Always pair what was run with what it returned.
56886
+ - If the conversation contains an analysis or post-mortem report (the begin_analysis tool marks where one starts), carry its content through verbatim in the relevant sections instead of re-summarizing it.
56887
+ - If an existing summary of older messages is provided, merge it with the new segment into ONE self-contained summary covering everything. Preserve all verbatim data from the existing summary unless the new segment explicitly supersedes it.
56888
+ - Do not add commentary, advice, or information that is not in the conversation.
56889
+ - Output only the summary markdown, nothing else.`;
56890
+ var TOOL_INPUT_CHAR_LIMIT = 2e3;
56891
+ var TOOL_OUTPUT_CHAR_LIMIT = 6e3;
56892
+ var GENERATION_TIMEOUT_MS = 5 * 6e4;
56893
+ function truncate(value, limit) {
56894
+ if (value === void 0) return "(none)";
56895
+ let text3;
56896
+ try {
56897
+ text3 = typeof value === "string" ? value : JSON.stringify(
56898
+ value,
56899
+ (_key, v) => typeof v === "string" && v.length > limit ? v.slice(0, limit) : v
56900
+ );
56901
+ } catch {
56902
+ text3 = String(value);
56903
+ }
56904
+ return text3.length > limit ? `${text3.slice(0, limit)}\u2026 (truncated)` : text3;
56905
+ }
56906
+ function serializeMessagesForSummary(messages) {
56907
+ const blocks = [];
56908
+ messages.forEach((msg, i) => {
56909
+ const lines = [`### Message ${i + 1} \u2014 ${msg.role}`];
56910
+ for (const part of msg.parts) {
56911
+ if (part.type === "text") {
56912
+ lines.push(part.text);
56913
+ continue;
56914
+ }
56915
+ if (part.type === "reasoning" || part.type === "step-start" || part.type.startsWith("data-") || part.type === "file") {
56916
+ continue;
56917
+ }
56918
+ const p = part;
56919
+ if (p.toolCallId) {
56920
+ const name26 = p.type.startsWith("tool-") ? p.type.slice(5) : p.type;
56921
+ const output = p.output !== void 0 ? p.output : p.errorText !== void 0 ? `error: ${p.errorText}` : void 0;
56922
+ lines.push(
56923
+ `[tool: ${name26}]`,
56924
+ `input: ${truncate(p.input, TOOL_INPUT_CHAR_LIMIT)}`,
56925
+ `output: ${truncate(output, TOOL_OUTPUT_CHAR_LIMIT)}`
56926
+ );
56927
+ }
56928
+ }
56929
+ blocks.push(lines.join("\n"));
56930
+ });
56931
+ return blocks.join("\n\n");
56932
+ }
56933
+ async function generateSessionSummary(db2, opts) {
56934
+ const resolved = resolveModel(db2);
56935
+ if ("error" in resolved) {
56936
+ console.warn("[summary] Cannot generate summary:", resolved.error);
56937
+ return { error: resolved.error, config: true };
56938
+ }
56939
+ const serialized = serializeMessagesForSummary(opts.messages);
56940
+ let userContent = opts.priorSummary ? `## Existing summary of older messages (merge into your output)
56941
+ ${opts.priorSummary}
56942
+
56943
+ ## New conversation segment to incorporate
56944
+ ${serialized}` : `## Conversation to summarize
56945
+ ${serialized}`;
56946
+ if (opts.keptAnalysis) {
56947
+ userContent += `
56948
+
56949
+ Note: the assistant's final analysis of the last exchange is preserved verbatim in the conversation right after your summary \u2014 record the work and results above without inventing or restating its conclusions.`;
56950
+ }
56951
+ try {
56952
+ const { text: text3, usage } = await generateText({
56953
+ model: resolved.model,
56954
+ temperature: 0,
56955
+ system: SUMMARY_SYSTEM_PROMPT,
56956
+ messages: [{ role: "user", content: userContent }],
56957
+ providerOptions: resolved.providerOptions,
56958
+ abortSignal: AbortSignal.timeout(GENERATION_TIMEOUT_MS)
56959
+ });
56960
+ const summary = text3.trim();
56961
+ if (!summary) return { error: "Summary generation returned no content" };
56962
+ recordAgentRun(db2, {
56963
+ sessionId: opts.sessionId,
56964
+ agentType: "summary",
56965
+ model: resolved.modelId,
56966
+ usage: extractUsage(usage, resolved.modelId)
56967
+ });
56968
+ return { summary };
56969
+ } catch (err) {
56970
+ console.warn("[summary] Failed to generate summary:", err);
56971
+ return { error: "Summary generation failed" };
56972
+ }
56973
+ }
56974
+
56772
56975
  // src/trpc/routers/sessions.router.ts
56773
56976
  var AGENT_TYPE_LABELS = {
56774
56977
  chat: "Chat",
@@ -56776,7 +56979,8 @@ var AGENT_TYPE_LABELS = {
56776
56979
  gcp: "GCP sub-agent",
56777
56980
  posthog: "PostHog sub-agent",
56778
56981
  title: "Title gen",
56779
- memory: "Memory"
56982
+ memory: "Memory",
56983
+ summary: "Compaction"
56780
56984
  };
56781
56985
  var sessionsRouter = router({
56782
56986
  list: publicProcedure.query(({ ctx }) => {
@@ -56811,7 +57015,10 @@ var sessionsRouter = router({
56811
57015
  status: row.status,
56812
57016
  kind: row.kind,
56813
57017
  messages,
56814
- updatedAt: row.updatedAt
57018
+ updatedAt: row.updatedAt,
57019
+ summary: row.summary,
57020
+ summaryUpTo: row.summaryUpTo,
57021
+ summaryCreatedAt: row.summaryCreatedAt
56815
57022
  };
56816
57023
  }),
56817
57024
  getCost: publicProcedure.input(external_exports.object({ id: external_exports.string() })).query(({ ctx, input }) => {
@@ -56904,19 +57111,104 @@ var sessionsRouter = router({
56904
57111
  return { id };
56905
57112
  }),
56906
57113
  truncateMessages: publicProcedure.input(external_exports.object({ id: external_exports.string(), keepCount: external_exports.number().int().min(0) })).mutation(({ ctx, input }) => {
56907
- const row = ctx.db.select({ messages: chatSessions.messages }).from(chatSessions).where(eq(chatSessions.id, input.id)).get();
56908
- if (!row) return { success: false };
57114
+ const row = ctx.db.select({ messages: chatSessions.messages, summaryUpTo: chatSessions.summaryUpTo }).from(chatSessions).where(eq(chatSessions.id, input.id)).get();
57115
+ if (!row) return { success: false, summaryCleared: false };
56909
57116
  let messages = [];
56910
57117
  try {
56911
57118
  messages = JSON.parse(row.messages);
56912
57119
  } catch {
56913
- return { success: false };
57120
+ return { success: false, summaryCleared: false };
56914
57121
  }
56915
57122
  const truncated = messages.slice(0, input.keepCount);
57123
+ const keptAnalysisBoundary = row.summaryUpTo != null && isAnalysisMessage(messages[row.summaryUpTo] ?? { role: "" });
57124
+ const sourceUpTo = row.summaryUpTo != null ? row.summaryUpTo + (keptAnalysisBoundary ? 1 : 0) : 0;
57125
+ const summaryStale = row.summaryUpTo != null && input.keepCount < sourceUpTo;
56916
57126
  ctx.db.update(chatSessions).set({
56917
57127
  messages: JSON.stringify(truncated),
56918
- updatedAt: unixNow()
57128
+ updatedAt: unixNow(),
57129
+ ...summaryStale ? { summary: null, summaryUpTo: null, summaryCreatedAt: null } : {}
56919
57130
  }).where(eq(chatSessions.id, input.id)).run();
57131
+ return { success: true, summaryCleared: summaryStale };
57132
+ }),
57133
+ compact: publicProcedure.input(external_exports.object({ id: external_exports.string(), upToIndex: external_exports.number().int().min(0) })).mutation(async ({ ctx, input }) => {
57134
+ const row = ctx.db.select().from(chatSessions).where(eq(chatSessions.id, input.id)).get();
57135
+ if (!row) throw new TRPCError({ code: "NOT_FOUND", message: "Session not found" });
57136
+ if (row.status === "streaming" || ctx.activeStreams.has(input.id)) {
57137
+ throw new TRPCError({ code: "CONFLICT", message: "Cannot compact while a response is in progress" });
57138
+ }
57139
+ let messages;
57140
+ try {
57141
+ messages = JSON.parse(row.messages);
57142
+ } catch {
57143
+ throw new TRPCError({ code: "INTERNAL_SERVER_ERROR", message: "Session messages are corrupted" });
57144
+ }
57145
+ const boundaryIdx = input.upToIndex;
57146
+ if (boundaryIdx >= messages.length) {
57147
+ throw new TRPCError({ code: "BAD_REQUEST", message: "Message not found in session" });
57148
+ }
57149
+ if (messages[boundaryIdx].role !== "assistant") {
57150
+ throw new TRPCError({ code: "BAD_REQUEST", message: "Can only compact up to an assistant message" });
57151
+ }
57152
+ const summaryUpTo = compactionUpTo(messages, boundaryIdx);
57153
+ if (summaryUpTo == null) {
57154
+ throw new TRPCError({ code: "BAD_REQUEST", message: "There are no messages to summarize before the analysis" });
57155
+ }
57156
+ const keptAnalysis = isAnalysisMessage(messages[boundaryIdx]);
57157
+ const incremental = !!row.summary && row.summaryUpTo != null && row.summaryUpTo < summaryUpTo;
57158
+ const segment = messages.slice(incremental ? row.summaryUpTo : 0, boundaryIdx + 1);
57159
+ if (incremental) {
57160
+ const head = segment[0];
57161
+ if (isAnalysisMessage(head)) {
57162
+ const split = splitAtAnalysis(head.parts);
57163
+ if (split) segment[0] = { ...head, parts: split.analysis };
57164
+ }
57165
+ }
57166
+ if (keptAnalysis) {
57167
+ const last = segment[segment.length - 1];
57168
+ const split = splitAtAnalysis(last.parts);
57169
+ if (split) segment[segment.length - 1] = { ...last, parts: split.before };
57170
+ }
57171
+ const result = await generateSessionSummary(ctx.db, {
57172
+ sessionId: input.id,
57173
+ priorSummary: incremental ? row.summary : void 0,
57174
+ messages: segment,
57175
+ keptAnalysis
57176
+ });
57177
+ if ("error" in result) {
57178
+ throw new TRPCError({
57179
+ code: result.config ? "PRECONDITION_FAILED" : "INTERNAL_SERVER_ERROR",
57180
+ message: result.error
57181
+ });
57182
+ }
57183
+ const fresh = ctx.db.select({ messages: chatSessions.messages, status: chatSessions.status }).from(chatSessions).where(eq(chatSessions.id, input.id)).get();
57184
+ const sourceUpTo = boundaryIdx + 1;
57185
+ let prefixUnchanged = false;
57186
+ if (fresh && fresh.status !== "streaming" && !ctx.activeStreams.has(input.id)) {
57187
+ try {
57188
+ const freshMessages = JSON.parse(fresh.messages);
57189
+ prefixUnchanged = JSON.stringify(freshMessages.slice(0, sourceUpTo)) === JSON.stringify(messages.slice(0, sourceUpTo));
57190
+ } catch {
57191
+ }
57192
+ }
57193
+ if (!prefixUnchanged) {
57194
+ throw new TRPCError({ code: "CONFLICT", message: "The conversation changed while the summary was being generated \u2014 try again" });
57195
+ }
57196
+ const summary = result.summary;
57197
+ const summaryCreatedAt = unixNow();
57198
+ ctx.db.update(chatSessions).set({ summary, summaryUpTo, summaryCreatedAt }).where(eq(chatSessions.id, input.id)).run();
57199
+ return { summary, summaryUpTo, summaryCreatedAt };
57200
+ }),
57201
+ updateSummary: publicProcedure.input(external_exports.object({ id: external_exports.string(), summary: external_exports.string().min(1) })).mutation(({ ctx, input }) => {
57202
+ const row = ctx.db.select({ summaryUpTo: chatSessions.summaryUpTo }).from(chatSessions).where(eq(chatSessions.id, input.id)).get();
57203
+ if (!row) throw new TRPCError({ code: "NOT_FOUND", message: "Session not found" });
57204
+ if (row.summaryUpTo == null) {
57205
+ throw new TRPCError({ code: "CONFLICT", message: "The summary no longer exists" });
57206
+ }
57207
+ ctx.db.update(chatSessions).set({ summary: input.summary }).where(eq(chatSessions.id, input.id)).run();
57208
+ return { success: true };
57209
+ }),
57210
+ clearSummary: publicProcedure.input(external_exports.object({ id: external_exports.string() })).mutation(({ ctx, input }) => {
57211
+ ctx.db.update(chatSessions).set({ summary: null, summaryUpTo: null, summaryCreatedAt: null }).where(eq(chatSessions.id, input.id)).run();
56920
57212
  return { success: true };
56921
57213
  })
56922
57214
  });
@@ -57495,7 +57787,11 @@ function loadSessionMessages(db2, sessionId, newMessage) {
57495
57787
  console.warn(`[chat] Corrupted session ${sessionId}, starting fresh`);
57496
57788
  }
57497
57789
  }
57498
- return [...sanitizeMessages(previous), newMessage];
57790
+ return {
57791
+ messages: [...sanitizeMessages(previous), newMessage],
57792
+ summary: existing?.summary ?? null,
57793
+ summaryUpTo: existing?.summaryUpTo ?? null
57794
+ };
57499
57795
  }
57500
57796
  function finalizeSession(sessionId, context2, broadcaster) {
57501
57797
  if (!context2.activeStreams.has(sessionId)) return;
@@ -57503,7 +57799,7 @@ function finalizeSession(sessionId, context2, broadcaster) {
57503
57799
  broadcaster.finish();
57504
57800
  context2.activeStreams.delete(sessionId);
57505
57801
  }
57506
- async function processLLMStream(sessionId, messages, context2, broadcaster, serverAbort, collectTools, sessionTitle, model, modelId, providerOptions) {
57802
+ async function processLLMStream(sessionId, messages, context2, broadcaster, serverAbort, collectTools, sessionTitle, model, modelId, providerOptions, compaction) {
57507
57803
  const writer = {
57508
57804
  write: (part) => {
57509
57805
  const p = part;
@@ -57514,7 +57810,29 @@ async function processLLMStream(sessionId, messages, context2, broadcaster, serv
57514
57810
  };
57515
57811
  const collected = collectTools(writer);
57516
57812
  const tools = collected.tools;
57517
- const modelMessages = await convertToModelMessages(messages, {
57813
+ let modelInput = messages;
57814
+ let summaryForPrompt = null;
57815
+ if (compaction.summary && compaction.summaryUpTo && compaction.summaryUpTo < messages.length) {
57816
+ const tail = messages.slice(compaction.summaryUpTo);
57817
+ if (tail[0].role === "user") {
57818
+ modelInput = tail;
57819
+ summaryForPrompt = compaction.summary;
57820
+ } else {
57821
+ const split = splitAtAnalysis(tail[0].parts);
57822
+ if (split && split.analysis.length > 0) {
57823
+ tail[0] = { ...tail[0], parts: split.analysis };
57824
+ modelInput = [
57825
+ { id: "", role: "user", parts: [{ type: "text", text: "(The conversation up to this point was compacted into the summary in your instructions.)" }] },
57826
+ ...tail
57827
+ ];
57828
+ summaryForPrompt = compaction.summary;
57829
+ }
57830
+ }
57831
+ }
57832
+ if (compaction.summary && !summaryForPrompt) {
57833
+ console.warn(`[chat] Ignoring stale compaction boundary for ${sessionId} (summaryUpTo=${compaction.summaryUpTo}, messages=${messages.length})`);
57834
+ }
57835
+ const modelMessages = await convertToModelMessages(modelInput, {
57518
57836
  tools,
57519
57837
  convertDataPart: () => void 0
57520
57838
  });
@@ -57535,6 +57853,16 @@ ${fragments.join("\n\n")}` : `${basePrompt}
57535
57853
  No observability providers are currently configured. If the user asks about observability data, let them know they can connect providers in the Settings page.`;
57536
57854
  }
57537
57855
  systemPrompt += "\n\n" + getCurrentDateBlock(context2.db);
57856
+ if (summaryForPrompt) {
57857
+ systemPrompt += `
57858
+
57859
+ ## Earlier conversation summary
57860
+ The earlier part of this conversation was compacted to save context. The summary below replaces those messages and is authoritative: the work it describes is already done \u2014 do NOT redo it. Reuse its recorded results, identifiers, queries, and conclusions.
57861
+
57862
+ <conversation_summary>
57863
+ ${summaryForPrompt}
57864
+ </conversation_summary>`;
57865
+ }
57538
57866
  const result = streamText({
57539
57867
  model,
57540
57868
  temperature: 0,
@@ -57648,7 +57976,7 @@ No observability providers are currently configured. If the user asks about obse
57648
57976
  clearTimeout(timeoutId);
57649
57977
  finalizeSession(sessionId, context2, broadcaster);
57650
57978
  }
57651
- async function runChatAgent({ sessionId, messages, context: context2, collectTools, sessionTitle, modelOverride }) {
57979
+ async function runChatAgent({ sessionId, messages, summary, summaryUpTo, context: context2, collectTools, sessionTitle, modelOverride }) {
57652
57980
  const resolved = modelOverride ?? resolveModel(context2.db);
57653
57981
  if ("error" in resolved) return { error: resolved.error };
57654
57982
  const { model, modelId, providerOptions } = resolved;
@@ -57680,7 +58008,8 @@ async function runChatAgent({ sessionId, messages, context: context2, collectToo
57680
58008
  sessionTitle,
57681
58009
  model,
57682
58010
  modelId,
57683
- providerOptions
58011
+ providerOptions,
58012
+ { summary, summaryUpTo }
57684
58013
  ).catch((err) => {
57685
58014
  console.error(`[chat] Unhandled error in LLM processing for ${sessionId}:`, err);
57686
58015
  finalizeSession(sessionId, context2, broadcaster);
@@ -57804,7 +58133,7 @@ function nextYPosition(db2, dashboardId) {
57804
58133
  }
57805
58134
  function collectDashboardTools(registry2, db2, writer, dashboardId) {
57806
58135
  const dbId = dashboardId ?? "";
57807
- const { tools, promptFragments, connectedProviders } = collectBaseTools(registry2, db2, writer);
58136
+ const { tools, promptFragments, connectedProviders } = collectBaseTools(registry2, db2, writer, "unified");
57808
58137
  const defaultProvider = connectedProviders[0];
57809
58138
  tools.create_widget = tool({
57810
58139
  description: "Create a new dashboard widget. The query will be validated by executing it first. The widget auto-positions below existing widgets.",
@@ -57943,7 +58272,7 @@ The global date picker already shows the active time range, so titles should des
57943
58272
 
57944
58273
  ## Scope
57945
58274
  You are managing widgets for the current dashboard only. The widget list above shows only this dashboard's widgets.`;
57946
- const systemPrompt = [basePrompt, providerContext, widgetContext, ...promptFragments].join("\n\n");
58275
+ const systemPrompt = [basePrompt, EVIDENCE_GROUNDING, providerContext, widgetContext, ...promptFragments].join("\n\n");
57947
58276
  return { tools, systemPrompt };
57948
58277
  }
57949
58278
 
@@ -58250,7 +58579,7 @@ function getMonitorContext(db2) {
58250
58579
  ${lines.join("\n")}`;
58251
58580
  }
58252
58581
  function collectMonitorTools(registry2, db2, writer) {
58253
- const { tools, promptFragments, connectedProviders } = collectBaseTools(registry2, db2, writer);
58582
+ const { tools, promptFragments, connectedProviders } = collectBaseTools(registry2, db2, writer, "unified");
58254
58583
  const defaultProvider = connectedProviders[0];
58255
58584
  tools.create_monitor = tool({
58256
58585
  description: "Create a new monitor that periodically checks a query and alerts when a condition is met.",
@@ -58408,7 +58737,7 @@ The condition is a JS expression evaluated against the query \`result\` array. E
58408
58737
 
58409
58738
  ## Scope
58410
58739
  You are managing all monitors. The monitor list above shows all existing monitors.`;
58411
- const systemPrompt = [basePrompt, providerContext, monitorContext, ...promptFragments].join("\n\n");
58740
+ const systemPrompt = [basePrompt, EVIDENCE_GROUNDING, providerContext, monitorContext, ...promptFragments].join("\n\n");
58412
58741
  return { tools, systemPrompt };
58413
58742
  }
58414
58743
 
@@ -58448,7 +58777,7 @@ function generateSessionTitle(db2, sessionId, userMessage) {
58448
58777
  function registerChatRoutes(app, context2) {
58449
58778
  app.post("/api/chat", async (c) => {
58450
58779
  const { id, message, activeProvider } = await c.req.json();
58451
- const messages = loadSessionMessages(context2.db, id, message);
58780
+ const { messages, summary, summaryUpTo } = loadSessionMessages(context2.db, id, message);
58452
58781
  if (messages.length === 1) {
58453
58782
  const textPart = message.parts?.find((p) => p.type === "text");
58454
58783
  if (textPart) {
@@ -58462,6 +58791,8 @@ function registerChatRoutes(app, context2) {
58462
58791
  const result = await runChatAgent({
58463
58792
  sessionId: id,
58464
58793
  messages,
58794
+ summary,
58795
+ summaryUpTo,
58465
58796
  context: context2,
58466
58797
  collectTools: (writer) => collectChatTools(context2.providers, context2.db, writer, scopedProvider, mode),
58467
58798
  sessionTitle: (updatedMessages) => {
@@ -58477,10 +58808,12 @@ function registerChatRoutes(app, context2) {
58477
58808
  app.post("/api/dashboard-chat", async (c) => {
58478
58809
  const { id, message, dashboardId } = await c.req.json();
58479
58810
  const sessionId = dashboardSessionId(dashboardId);
58480
- const messages = loadSessionMessages(context2.db, sessionId, message);
58811
+ const { messages, summary, summaryUpTo } = loadSessionMessages(context2.db, sessionId, message);
58481
58812
  const result = await runChatAgent({
58482
58813
  sessionId,
58483
58814
  messages,
58815
+ summary,
58816
+ summaryUpTo,
58484
58817
  context: context2,
58485
58818
  collectTools: (writer) => collectDashboardTools(context2.providers, context2.db, writer, dashboardId),
58486
58819
  sessionTitle: () => "Dashboard Builder"
@@ -58491,10 +58824,12 @@ function registerChatRoutes(app, context2) {
58491
58824
  app.post("/api/monitor-chat", async (c) => {
58492
58825
  const { message } = await c.req.json();
58493
58826
  const sessionId = SESSION_PREFIX.MONITORS;
58494
- const messages = loadSessionMessages(context2.db, sessionId, message);
58827
+ const { messages, summary, summaryUpTo } = loadSessionMessages(context2.db, sessionId, message);
58495
58828
  const result = await runChatAgent({
58496
58829
  sessionId,
58497
58830
  messages,
58831
+ summary,
58832
+ summaryUpTo,
58498
58833
  context: context2,
58499
58834
  collectTools: (writer) => collectMonitorTools(context2.providers, context2.db, writer),
58500
58835
  sessionTitle: () => "Monitor Builder"
@@ -58645,7 +58980,7 @@ function registerApiRoutes(app, context2) {
58645
58980
  role: "user",
58646
58981
  parts: [{ type: "text", text: message }]
58647
58982
  };
58648
- const messages = loadSessionMessages(context2.db, sessionId, userMessage);
58983
+ const { messages, summary, summaryUpTo } = loadSessionMessages(context2.db, sessionId, userMessage);
58649
58984
  if (messages.length === 1) {
58650
58985
  generateSessionTitle(context2.db, sessionId, message);
58651
58986
  }
@@ -58658,6 +58993,8 @@ function registerApiRoutes(app, context2) {
58658
58993
  const result = await runChatAgent({
58659
58994
  sessionId,
58660
58995
  messages,
58996
+ summary,
58997
+ summaryUpTo,
58661
58998
  context: context2,
58662
58999
  collectTools: (writer) => {
58663
59000
  const collected = collectChatTools(context2.providers, context2.db, writer, scopedProvider, mode);
@@ -1 +1 @@
1
- import{a as q,r as s,j as r}from"./index-B7y1naN3.js";var P=q();function $(c){const n=c?`tracer:starred:${c}`:null,[i,S]=s.useState(()=>{if(!n)return new Set;try{const a=localStorage.getItem(n);return a?new Set(JSON.parse(a)):new Set}catch{return new Set}});return[i,a=>{S(u=>{const t=new Set(u);return t.has(a)?t.delete(a):t.add(a),n&&localStorage.setItem(n,JSON.stringify([...t])),t})}]}function I({options:c,value:n,onChange:i,placeholder:S="Select...",storageKey:v,fitContent:a,disabled:u}){const[t,l]=s.useState(!1),[w,y]=s.useState(""),f=s.useRef(null),j=s.useRef(null),N=s.useRef(null),g=s.useRef(null),[x,C]=$(v),[m,L]=s.useState({top:0,left:0,minWidth:0});s.useEffect(()=>{if(!t)return;const e=d=>{f.current?.contains(d.target)||j.current?.contains(d.target)||l(!1)};return document.addEventListener("mousedown",e),()=>document.removeEventListener("mousedown",e)},[t]);const R=s.useCallback(()=>{if(!f.current)return;const e=f.current.getBoundingClientRect();L({top:e.bottom+4,left:e.left,minWidth:e.width})},[]);s.useEffect(()=>{t&&(R(),y(""),requestAnimationFrame(()=>N.current?.focus()))},[t,R]);const h=c.find(e=>e.value===n),p=s.useMemo(()=>{const e=w.toLowerCase();return[...e?c.filter(o=>o.label.toLowerCase().includes(e)||o.value.toLowerCase().includes(e)):c].sort((o,b)=>{const k=x.has(o.value)?0:1,E=x.has(b.value)?0:1;return k!==E?k-E:o.label.localeCompare(b.label)})},[c,w,x]);s.useEffect(()=>{if(t&&n&&g.current){const e=g.current.querySelector(`[data-value="${CSS.escape(n)}"]`);e&&e.scrollIntoView({block:"nearest"})}},[t,n]);const W=t?P.createPortal(r.jsxs("div",{ref:j,className:"fixed z-[100] bg-white border border-[#d4d2cd] rounded shadow-lg",style:{top:m.top,left:m.left,minWidth:m.minWidth,width:a?"max-content":m.minWidth},children:[r.jsx("div",{className:"p-1.5 border-b border-[#e8e6e1]",children:r.jsx("input",{ref:N,type:"text",value:w,onChange:e=>y(e.target.value),placeholder:"Search...",className:"w-full px-2 py-1.5 text-xs text-[#2c2c2c] font-sans bg-[#f5f4f0] border border-[#e8e6e1] rounded focus:outline-none focus:border-[#2b5ea7] placeholder:text-[#9c9890]",onKeyDown:e=>{e.key==="Escape"&&l(!1),e.key==="Enter"&&p.length>0&&(i(p[0].value),l(!1))}})}),r.jsx("div",{ref:g,className:"max-h-[280px] overflow-y-auto",children:p.length===0?r.jsx("div",{className:"px-3 py-3 text-xs text-[#9c9890] text-center",children:"No projects found"}):p.map(e=>{const d=e.value===n,o=x.has(e.value);return r.jsxs("div",{"data-value":e.value,className:`flex items-center gap-1.5 px-2 py-1.5 text-xs font-sans cursor-pointer transition-colors ${d?"bg-[#2b5ea7]/10 text-[#2b5ea7]":"text-[#2c2c2c] hover:bg-[#f5f4f0]"}`,onClick:()=>{i(e.value),l(!1)},children:[r.jsx("button",{type:"button",onClick:b=>{b.stopPropagation(),C(e.value)},className:`shrink-0 w-4 h-4 flex items-center justify-center text-[10px] transition-colors ${o?"text-[#d4a017]":"text-[#d4d2cd] hover:text-[#9c9890]"}`,title:o?"Unstar":"Star to pin to top",children:o?"★":"☆"}),r.jsx("span",{className:a?"whitespace-nowrap":"truncate flex-1",children:e.label})]},e.value)})})]}),document.body):null;return r.jsxs(r.Fragment,{children:[r.jsxs("button",{ref:f,type:"button",onClick:()=>!u&&l(!t),disabled:u,className:"w-full bg-white border border-[#d4d2cd] rounded px-3 py-2 text-xs text-[#2c2c2c] font-sans text-left flex items-center justify-between focus:outline-none focus:border-[#2b5ea7] hover:border-[#b0ada6] transition-colors disabled:opacity-50 disabled:cursor-not-allowed",children:[r.jsx("span",{className:h?"text-[#2c2c2c] truncate":"text-[#9c9890]",children:h?h.displayLabel??h.label:S}),r.jsx("span",{className:"text-[#9c9890] text-[10px] ml-2 shrink-0 transition-transform duration-200",style:{transform:t?"rotate(180deg)":"rotate(0deg)"},children:"▾"})]}),W]})}export{I as S,P as r,$ as u};
1
+ import{a as q,r as s,j as r}from"./index-IihLiR83.js";var P=q();function $(c){const n=c?`tracer:starred:${c}`:null,[i,S]=s.useState(()=>{if(!n)return new Set;try{const a=localStorage.getItem(n);return a?new Set(JSON.parse(a)):new Set}catch{return new Set}});return[i,a=>{S(u=>{const t=new Set(u);return t.has(a)?t.delete(a):t.add(a),n&&localStorage.setItem(n,JSON.stringify([...t])),t})}]}function I({options:c,value:n,onChange:i,placeholder:S="Select...",storageKey:v,fitContent:a,disabled:u}){const[t,l]=s.useState(!1),[w,y]=s.useState(""),f=s.useRef(null),j=s.useRef(null),N=s.useRef(null),g=s.useRef(null),[x,C]=$(v),[m,L]=s.useState({top:0,left:0,minWidth:0});s.useEffect(()=>{if(!t)return;const e=d=>{f.current?.contains(d.target)||j.current?.contains(d.target)||l(!1)};return document.addEventListener("mousedown",e),()=>document.removeEventListener("mousedown",e)},[t]);const R=s.useCallback(()=>{if(!f.current)return;const e=f.current.getBoundingClientRect();L({top:e.bottom+4,left:e.left,minWidth:e.width})},[]);s.useEffect(()=>{t&&(R(),y(""),requestAnimationFrame(()=>N.current?.focus()))},[t,R]);const h=c.find(e=>e.value===n),p=s.useMemo(()=>{const e=w.toLowerCase();return[...e?c.filter(o=>o.label.toLowerCase().includes(e)||o.value.toLowerCase().includes(e)):c].sort((o,b)=>{const k=x.has(o.value)?0:1,E=x.has(b.value)?0:1;return k!==E?k-E:o.label.localeCompare(b.label)})},[c,w,x]);s.useEffect(()=>{if(t&&n&&g.current){const e=g.current.querySelector(`[data-value="${CSS.escape(n)}"]`);e&&e.scrollIntoView({block:"nearest"})}},[t,n]);const W=t?P.createPortal(r.jsxs("div",{ref:j,className:"fixed z-[100] bg-white border border-[#d4d2cd] rounded shadow-lg",style:{top:m.top,left:m.left,minWidth:m.minWidth,width:a?"max-content":m.minWidth},children:[r.jsx("div",{className:"p-1.5 border-b border-[#e8e6e1]",children:r.jsx("input",{ref:N,type:"text",value:w,onChange:e=>y(e.target.value),placeholder:"Search...",className:"w-full px-2 py-1.5 text-xs text-[#2c2c2c] font-sans bg-[#f5f4f0] border border-[#e8e6e1] rounded focus:outline-none focus:border-[#2b5ea7] placeholder:text-[#9c9890]",onKeyDown:e=>{e.key==="Escape"&&l(!1),e.key==="Enter"&&p.length>0&&(i(p[0].value),l(!1))}})}),r.jsx("div",{ref:g,className:"max-h-[280px] overflow-y-auto",children:p.length===0?r.jsx("div",{className:"px-3 py-3 text-xs text-[#9c9890] text-center",children:"No projects found"}):p.map(e=>{const d=e.value===n,o=x.has(e.value);return r.jsxs("div",{"data-value":e.value,className:`flex items-center gap-1.5 px-2 py-1.5 text-xs font-sans cursor-pointer transition-colors ${d?"bg-[#2b5ea7]/10 text-[#2b5ea7]":"text-[#2c2c2c] hover:bg-[#f5f4f0]"}`,onClick:()=>{i(e.value),l(!1)},children:[r.jsx("button",{type:"button",onClick:b=>{b.stopPropagation(),C(e.value)},className:`shrink-0 w-4 h-4 flex items-center justify-center text-[10px] transition-colors ${o?"text-[#d4a017]":"text-[#d4d2cd] hover:text-[#9c9890]"}`,title:o?"Unstar":"Star to pin to top",children:o?"★":"☆"}),r.jsx("span",{className:a?"whitespace-nowrap":"truncate flex-1",children:e.label})]},e.value)})})]}),document.body):null;return r.jsxs(r.Fragment,{children:[r.jsxs("button",{ref:f,type:"button",onClick:()=>!u&&l(!t),disabled:u,className:"w-full bg-white border border-[#d4d2cd] rounded px-3 py-2 text-xs text-[#2c2c2c] font-sans text-left flex items-center justify-between focus:outline-none focus:border-[#2b5ea7] hover:border-[#b0ada6] transition-colors disabled:opacity-50 disabled:cursor-not-allowed",children:[r.jsx("span",{className:h?"text-[#2c2c2c] truncate":"text-[#9c9890]",children:h?h.displayLabel??h.label:S}),r.jsx("span",{className:"text-[#9c9890] text-[10px] ml-2 shrink-0 transition-transform duration-200",style:{transform:t?"rotate(180deg)":"rotate(0deg)"},children:"▾"})]}),W]})}export{I as S,P as r,$ as u};