tracer-sh 0.1.0 → 0.2.0

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.
@@ -13,9 +13,13 @@ import {
13
13
  GCP_CROSS_SIGNAL,
14
14
  GCP_DOMAIN_KNOWLEDGE,
15
15
  GCP_INSIDE_OUT_DEBUGGING,
16
- GCP_PAGE_SIZE_RULE,
17
- GCP_TOOL_REFERENCE
16
+ GCP_PAGE_SIZE_RULE
18
17
  } from "./chunk-6QUU7TGZ.js";
18
+ import {
19
+ POSTHOG_AUTH_STOP_RULE,
20
+ POSTHOG_DOMAIN_KNOWLEDGE,
21
+ POSTHOG_INSIDE_OUT_DEBUGGING
22
+ } from "./chunk-ANVLQIEK.js";
19
23
  import {
20
24
  __commonJS,
21
25
  __export,
@@ -2748,6 +2752,7 @@ function formatTimestamps(results) {
2748
2752
  // ../shared/src/constants.ts
2749
2753
  var DEFAULT_SESSION_TITLE = "New chat";
2750
2754
  var DEFAULT_CHAT_MODE = "direct";
2755
+ var UNIFIED_SCOPE = "__unified__";
2751
2756
  var SESSION_PREFIX = {
2752
2757
  DASHBOARD: "__dashboard__",
2753
2758
  MONITORS: "__monitors__"
@@ -16549,16 +16554,6 @@ var ImportedAnalysisSchema = external_exports.object({
16549
16554
  parts: external_exports.array(external_exports.looseObject({ type: external_exports.string() })).max(200)
16550
16555
  });
16551
16556
 
16552
- // ../shared/src/feature-flags.ts
16553
- var FEATURES = {
16554
- /** Two-agent orchestrator mode (disabled — direct mode only). */
16555
- orchestratorMode: false,
16556
- /** Dashboard page with grid widgets. */
16557
- dashboards: false,
16558
- /** Monitor page with alerting. */
16559
- monitors: false
16560
- };
16561
-
16562
16557
  // src/config.ts
16563
16558
  var CONFIG = {
16564
16559
  /** HTTP server port. Override with TRACER_PORT env var. */
@@ -16569,7 +16564,7 @@ var CONFIG = {
16569
16564
  corsOrigin: process.env.TRACER_CORS_ORIGIN ?? null,
16570
16565
  // ── LLM defaults ──
16571
16566
  defaultChatModel: { provider: "google", modelId: "gemini-3.1-pro-preview" },
16572
- defaultSubAgentModel: { provider: "google", modelId: "gemini-3-flash-preview" },
16567
+ defaultSubAgentModel: { provider: "google", modelId: "gemini-3.1-pro-preview" },
16573
16568
  defaultUtilityModel: { provider: "google", modelId: "gemini-3.1-flash-lite-preview" },
16574
16569
  /** Models that support thinking/reasoning tokens. */
16575
16570
  thinkingModels: /* @__PURE__ */ new Set(["gemini-3.1-pro-preview", "gemini-3-flash-preview"]),
@@ -16579,6 +16574,13 @@ var CONFIG = {
16579
16574
  mcpPingTimeoutMs: 5e3,
16580
16575
  // ── Agent result sizing ──
16581
16576
  maxModelResultChars: 8e3,
16577
+ /**
16578
+ * Code-level cap on the reasoning ("thinking") text a model may stream within a
16579
+ * single step. Enforced in the stream loops (not just via the provider
16580
+ * thinkingBudget, which models sometimes ignore and loop indefinitely) — when a
16581
+ * step exceeds this, the stream is aborted programmatically. Reset each step.
16582
+ */
16583
+ maxReasoningCharsPerStep: 4e4,
16582
16584
  // ── Monitor scheduler ──
16583
16585
  monitorTickIntervalMs: 1e4,
16584
16586
  monitorQueryTimeoutMs: 3e4,
@@ -16602,7 +16604,6 @@ var DEFAULTS = {
16602
16604
  };
16603
16605
  var SETTINGS_KEYS = {
16604
16606
  chatModel: "chat_model",
16605
- chatMode: "chat_mode",
16606
16607
  timezone: "timezone",
16607
16608
  directModeMaxSteps: "direct_mode_max_steps",
16608
16609
  subAgentMaxSteps: "sub_agent_max_steps",
@@ -37857,6 +37858,25 @@ var _a20;
37857
37858
  _a20 = symbol20;
37858
37859
  var defaultDownload2 = createDownload();
37859
37860
 
37861
+ // src/agents/chat/sub-agent.ts
37862
+ var MEMORY_SECTION_NAME = "Corrections from Previous Sessions";
37863
+ function injectMemories(prompt, memoryContext) {
37864
+ if (!memoryContext?.existingMemories.length) return prompt;
37865
+ const lines = memoryContext.existingMemories.filter((m) => m.note).map((m) => `- ${m.note}`);
37866
+ if (!lines.length) return prompt;
37867
+ const memoryBlock = `
37868
+
37869
+ ## ${MEMORY_SECTION_NAME}
37870
+ These OVERRIDE any conflicting instructions above \u2014 they are verified fixes from past errors:
37871
+ ${lines.join("\n")}
37872
+ `;
37873
+ const firstBreak = prompt.indexOf("\n\n");
37874
+ if (firstBreak !== -1) {
37875
+ return prompt.slice(0, firstBreak) + memoryBlock + prompt.slice(firstBreak);
37876
+ }
37877
+ return memoryBlock + prompt;
37878
+ }
37879
+
37860
37880
  // ../../node_modules/.pnpm/@ai-sdk+anthropic@3.0.71_zod@4.3.6/node_modules/@ai-sdk/anthropic/dist/index.mjs
37861
37881
  var VERSION5 = true ? "3.0.71" : "0.0.0-test";
37862
37882
  var anthropicErrorDataSchema = lazySchema(
@@ -45999,19 +46019,6 @@ function extractUsage(usage, model) {
45999
46019
  cacheWriteTokens: usage.inputTokenDetails?.cacheWriteTokens ?? 0
46000
46020
  };
46001
46021
  }
46002
- function emptyUsage(model = "") {
46003
- return { model, inputTokens: 0, outputTokens: 0, reasoningTokens: 0, cachedInputTokens: 0, cacheWriteTokens: 0 };
46004
- }
46005
- function addTokenUsage(a, b) {
46006
- return {
46007
- model: a.model,
46008
- inputTokens: a.inputTokens + b.inputTokens,
46009
- outputTokens: a.outputTokens + b.outputTokens,
46010
- reasoningTokens: a.reasoningTokens + b.reasoningTokens,
46011
- cachedInputTokens: a.cachedInputTokens + b.cachedInputTokens,
46012
- cacheWriteTokens: a.cacheWriteTokens + b.cacheWriteTokens
46013
- };
46014
- }
46015
46022
  function recordAgentRun(db2, opts) {
46016
46023
  try {
46017
46024
  db2.insert(agentRuns).values({
@@ -46127,7 +46134,8 @@ function createDeleteMemoryTool(memoryExecute, opts) {
46127
46134
  // src/agents/utility/memory-domain-knowledge.ts
46128
46135
  var DOMAIN_LOADERS = {
46129
46136
  newrelic: async () => (await import("./domain-knowledge-WHIEZOOH.js")).NR_DOMAIN_KNOWLEDGE,
46130
- gcp: async () => (await import("./domain-knowledge-V6AENXZV.js")).GCP_DOMAIN_KNOWLEDGE
46137
+ gcp: async () => (await import("./domain-knowledge-V6AENXZV.js")).GCP_DOMAIN_KNOWLEDGE,
46138
+ posthog: async () => (await import("./domain-knowledge-5JOEEXGN.js")).POSTHOG_DOMAIN_KNOWLEDGE
46131
46139
  };
46132
46140
  async function getDomainKnowledge(providerType) {
46133
46141
  const loader = DOMAIN_LOADERS[providerType];
@@ -46294,327 +46302,6 @@ ${tailInstruction}`;
46294
46302
  }
46295
46303
  }
46296
46304
 
46297
- // src/lib/current-context.ts
46298
- function getCurrentDateBlock(db2) {
46299
- const timezone = (db2 ? readAppSetting(db2, SETTINGS_KEYS.timezone) : null) ?? process.env.TRACER_TIMEZONE ?? DEFAULTS.timezone;
46300
- const now2 = /* @__PURE__ */ new Date();
46301
- const formatted = new Intl.DateTimeFormat("en-US", {
46302
- timeZone: timezone,
46303
- weekday: "long",
46304
- year: "numeric",
46305
- month: "long",
46306
- day: "numeric",
46307
- hour: "2-digit",
46308
- minute: "2-digit",
46309
- timeZoneName: "short"
46310
- }).format(now2);
46311
- return `## Current Date & Time
46312
- ${formatted}`;
46313
- }
46314
-
46315
- // src/agents/chat/sub-agent.ts
46316
- var MEMORY_SECTION_NAME = "Corrections from Previous Sessions";
46317
- function injectMemories(prompt, memoryContext) {
46318
- if (!memoryContext?.existingMemories.length) return prompt;
46319
- const lines = memoryContext.existingMemories.filter((m) => m.note).map((m) => `- ${m.note}`);
46320
- if (!lines.length) return prompt;
46321
- const memoryBlock = `
46322
-
46323
- ## ${MEMORY_SECTION_NAME}
46324
- These OVERRIDE any conflicting instructions above \u2014 they are verified fixes from past errors:
46325
- ${lines.join("\n")}
46326
- `;
46327
- const firstBreak = prompt.indexOf("\n\n");
46328
- if (firstBreak !== -1) {
46329
- return prompt.slice(0, firstBreak) + memoryBlock + prompt.slice(firstBreak);
46330
- }
46331
- return memoryBlock + prompt;
46332
- }
46333
- function subAgentModelOutput(output) {
46334
- if (!output) return { type: "text", value: "(no output)" };
46335
- if (typeof output === "object" && "error" in output) {
46336
- return { type: "text", value: output.error };
46337
- }
46338
- if (typeof output === "object" && "analysis" in output) {
46339
- return { type: "text", value: output.analysis };
46340
- }
46341
- return { type: "text", value: "(aborted)" };
46342
- }
46343
- async function runSubAgent(opts) {
46344
- const {
46345
- providerType,
46346
- db: db2,
46347
- systemPrompt,
46348
- task,
46349
- toolCallId,
46350
- queryTools,
46351
- queryToolNames,
46352
- collectedQueries,
46353
- memoryContext,
46354
- writer,
46355
- sessionId,
46356
- abortSignal
46357
- } = opts;
46358
- const resolved = resolveSubAgentModel(db2, providerType);
46359
- if ("error" in resolved) {
46360
- return { error: resolved.error };
46361
- }
46362
- const MAX_STEPS = opts.maxSteps ?? readAppSetting(db2, SETTINGS_KEYS.subAgentMaxSteps) ?? DEFAULTS.subAgentMaxSteps;
46363
- const startTime = Date.now();
46364
- const subAgentTools = { ...queryTools };
46365
- const fullSystemPrompt = injectMemories(systemPrompt, memoryContext) + "\n\n" + getCurrentDateBlock(db2);
46366
- const queryToolNameSet = new Set(queryToolNames);
46367
- const prompt = `Task: ${task}`;
46368
- const MAX_ATTEMPTS = 2;
46369
- for (let attempt = 0; attempt < MAX_ATTEMPTS; attempt++) {
46370
- collectedQueries.length = 0;
46371
- const isLastAttempt = attempt === MAX_ATTEMPTS - 1;
46372
- const activeWriter = isLastAttempt ? writer : void 0;
46373
- try {
46374
- const result = streamText({
46375
- model: resolved.model,
46376
- temperature: 0,
46377
- system: fullSystemPrompt,
46378
- prompt,
46379
- tools: subAgentTools,
46380
- stopWhen: stepCountIs(MAX_STEPS),
46381
- providerOptions: resolved.providerOptions,
46382
- experimental_transform: smoothStream({ chunking: "word" }),
46383
- abortSignal
46384
- });
46385
- let analysisText = "";
46386
- let stepCount = 0;
46387
- let lastFinishReason;
46388
- const parts = [];
46389
- for await (const part of result.fullStream) {
46390
- if (part.type === "tool-call" && queryToolNameSet.has(part.toolName)) {
46391
- activeWriter?.write({
46392
- type: "data-provider-part",
46393
- data: { toolCallId, part: { type: "tool-call", toolName: part.toolName } }
46394
- });
46395
- } else if (part.type === "tool-result" && queryToolNameSet.has(part.toolName)) {
46396
- const lastQuery = collectedQueries[collectedQueries.length - 1];
46397
- if (lastQuery) {
46398
- const qPart = { type: "query", query: lastQuery.query, results: lastQuery.results };
46399
- parts.push(qPart);
46400
- activeWriter?.write({
46401
- type: "data-provider-part",
46402
- data: { toolCallId, part: qPart }
46403
- });
46404
- }
46405
- } else if (part.type === "reasoning-delta") {
46406
- const lastPart = parts[parts.length - 1];
46407
- if (lastPart?.type === "reasoning") {
46408
- lastPart.content += part.text;
46409
- } else {
46410
- parts.push({ type: "reasoning", content: part.text });
46411
- }
46412
- activeWriter?.write({
46413
- type: "data-provider-part",
46414
- data: { toolCallId, part: { type: "reasoning-delta", delta: part.text } }
46415
- });
46416
- } else if (part.type === "text-delta") {
46417
- analysisText += part.text;
46418
- const lastPart = parts[parts.length - 1];
46419
- if (lastPart?.type === "text") {
46420
- lastPart.content += part.text;
46421
- } else {
46422
- parts.push({ type: "text", content: part.text });
46423
- }
46424
- activeWriter?.write({
46425
- type: "data-provider-part",
46426
- data: { toolCallId, part: { type: "text-delta", delta: part.text } }
46427
- });
46428
- } else if (part.type === "finish-step") {
46429
- stepCount++;
46430
- if ("finishReason" in part) {
46431
- lastFinishReason = part.finishReason;
46432
- }
46433
- }
46434
- }
46435
- const totalUsage = await result.totalUsage;
46436
- const investigationUsage = extractUsage(totalUsage, resolved.modelId);
46437
- const truncated = stepCount >= MAX_STEPS;
46438
- let extraUsage = emptyUsage(resolved.modelId);
46439
- if (truncated) {
46440
- let summary;
46441
- try {
46442
- const MAX_QUERY_CONTEXT = 12e3;
46443
- let queryContextLen = 0;
46444
- const querySummaries = collectedQueries.map((q, i) => {
46445
- if (queryContextLen >= MAX_QUERY_CONTEXT) return null;
46446
- const queryStr = q.query.slice(0, 300);
46447
- let resultSnippet;
46448
- const isErr = q.results && typeof q.results === "object" && "error" in q.results;
46449
- if (isErr) {
46450
- const errMsg = String(q.results.error ?? "unknown error").slice(0, 500);
46451
- resultSnippet = `ERROR: ${errMsg}`;
46452
- } else if (q.results != null) {
46453
- resultSnippet = JSON.stringify(q.results).slice(0, 500);
46454
- } else {
46455
- resultSnippet = "(no results)";
46456
- }
46457
- const entry = `<query index="${i + 1}">
46458
- <request>${queryStr}</request>
46459
- <result>${resultSnippet}</result>
46460
- </query>`;
46461
- if (queryContextLen + entry.length > MAX_QUERY_CONTEXT) return null;
46462
- queryContextLen += entry.length;
46463
- return entry;
46464
- }).filter(Boolean).join("\n");
46465
- const summaryResult = await generateText({
46466
- model: resolved.model,
46467
- temperature: 0,
46468
- system: `You distill a truncated investigation into a factual briefing. The orchestrator has ZERO access to raw query results \u2014 your summary is its ONLY source of information. Everything not in this summary is permanently lost.
46469
-
46470
- Rules:
46471
- - **Preserve every concrete data point.** Service names, error messages, counts, percentages, timestamps, trace IDs, hostnames, endpoints \u2014 if the investigation discovered it, include it. The orchestrator cannot recover data you omit.
46472
- - **Distinguish observations from inferences.** If the sub-agent stated a conclusion, note whether it was directly supported by query data or was an inference. Flag any unsupported claims.
46473
- - **Capture query patterns.** Which filters, field names, and syntax worked vs. failed \u2014 so follow-up calls avoid repeating mistakes.
46474
- - **List remaining work as actionable tasks.** Each item must be a complete standalone brief with all necessary context (identifiers, filters, event types) so a fresh sub-agent could execute it immediately.
46475
- - Complete all four sections fully \u2014 never cut off mid-sentence.
46476
- - Do NOT repeat the original task description.`,
46477
- prompt: `<context>
46478
- <task>${task}</task>
46479
- <steps_used>${stepCount} of ${MAX_STEPS}</steps_used>
46480
- <query_count>${collectedQueries.length}</query_count>
46481
- </context>
46482
-
46483
- <queries>
46484
- ${querySummaries}
46485
- </queries>
46486
-
46487
- <partial_analysis>
46488
- ${analysisText.slice(0, 1e4)}
46489
- </partial_analysis>
46490
-
46491
- <instructions>
46492
- Write exactly four sections. Every section must be complete \u2014 do not leave any sentence unfinished.
46493
-
46494
- 1. **Completed work** \u2014 bullet list of what was queried and investigated
46495
- 2. **Key findings** \u2014 bullet list of specific data points, values, and conclusions discovered so far (include numbers, names, IDs). Label each bullet as [OBSERVATION] or [INFERENCE]. For inferences, cite the observation that supports it.
46496
- 3. **Query patterns** \u2014 which query syntax, field names, and filters returned valid results vs which ones failed or returned empty. Include the correct syntax so follow-up queries can reuse it.
46497
- 4. **Remaining work** \u2014 bullet list of concrete next-step queries or checks needed to finish the task. Each item must include all necessary identifiers so a fresh sub-agent can act immediately.
46498
- </instructions>`,
46499
- maxOutputTokens: 6e3,
46500
- abortSignal
46501
- });
46502
- summary = summaryResult.text;
46503
- extraUsage = extractUsage(summaryResult.usage, resolved.modelId);
46504
- } catch {
46505
- const queryList = collectedQueries.map((q, i) => `${i + 1}. ${q.query.slice(0, 300)}`).join("\n");
46506
- summary = `Queries executed: ${collectedQueries.length}
46507
- ${queryList}`;
46508
- }
46509
- const notice = `
46510
-
46511
- ---
46512
- **Investigation truncated** (${stepCount}/${MAX_STEPS} steps)
46513
-
46514
- ${summary}
46515
-
46516
- *Consider a follow-up call with a focused task covering the remaining work.*`;
46517
- analysisText += notice;
46518
- const lastPart = parts[parts.length - 1];
46519
- if (lastPart?.type === "text") {
46520
- lastPart.content += notice;
46521
- } else {
46522
- parts.push({ type: "text", content: notice });
46523
- }
46524
- activeWriter?.write({
46525
- type: "data-provider-part",
46526
- data: { toolCallId, part: { type: "text-delta", delta: notice } }
46527
- });
46528
- }
46529
- for (let i = parts.length - 1; i >= 0; i--) {
46530
- if (parts[i].type === "text") {
46531
- parts[i].type = "summary";
46532
- activeWriter?.write({
46533
- type: "data-provider-part",
46534
- data: { toolCallId, part: { type: "mark-summary" } }
46535
- });
46536
- break;
46537
- }
46538
- }
46539
- const combinedUsage = addTokenUsage(investigationUsage, extraUsage);
46540
- const subAgentResult = {
46541
- analysis: analysisText,
46542
- parts,
46543
- truncated,
46544
- stepCount,
46545
- model: resolved.modelId,
46546
- provider: providerType,
46547
- inputTokens: combinedUsage.inputTokens,
46548
- outputTokens: combinedUsage.outputTokens,
46549
- reasoningTokens: combinedUsage.reasoningTokens,
46550
- cachedInputTokens: combinedUsage.cachedInputTokens,
46551
- cacheWriteTokens: combinedUsage.cacheWriteTokens
46552
- };
46553
- const resolvedSessionId = sessionId ?? writer?.sessionId;
46554
- const durationMs = Date.now() - startTime;
46555
- if (memoryContext) {
46556
- if (resolvedSessionId) {
46557
- db2.insert(memoryOperations).values({
46558
- sessionId: resolvedSessionId,
46559
- operation: "started"
46560
- }).run();
46561
- }
46562
- runMemoryAgent({
46563
- providerType,
46564
- db: db2,
46565
- existingMemories: memoryContext.existingMemories,
46566
- task,
46567
- analysisText,
46568
- collectedQueries,
46569
- sessionId: resolvedSessionId
46570
- }).catch((err) => {
46571
- console.warn(`[memory-agent] ${providerType} failed:`, err);
46572
- });
46573
- }
46574
- if (resolvedSessionId) {
46575
- recordAgentRun(db2, {
46576
- sessionId: resolvedSessionId,
46577
- agentType: providerType,
46578
- model: resolved.modelId,
46579
- usage: combinedUsage,
46580
- durationMs
46581
- });
46582
- }
46583
- const errorCount = collectedQueries.filter(
46584
- (q) => q.results && typeof q.results === "object" && "error" in q.results
46585
- ).length;
46586
- try {
46587
- db2.insert(subAgentRuns).values({
46588
- id: crypto.randomUUID(),
46589
- sessionId: resolvedSessionId ?? null,
46590
- provider: providerType,
46591
- task: task.slice(0, 500),
46592
- queryCount: collectedQueries.length,
46593
- errorCount,
46594
- stepCount,
46595
- truncated: truncated ? 1 : 0,
46596
- durationMs,
46597
- finishReason: lastFinishReason ?? null
46598
- }).run();
46599
- } catch (err) {
46600
- console.warn(`[sub-agent-telemetry] Failed to record run:`, err);
46601
- }
46602
- return subAgentResult;
46603
- } catch (err) {
46604
- if (abortSignal?.aborted || err instanceof Error && err.name === "AbortError") {
46605
- return { error: "Request was aborted." };
46606
- }
46607
- if (attempt === 0) {
46608
- console.warn(`[sub-agent] ${providerType} attempt 1 failed, retrying:`, err);
46609
- continue;
46610
- }
46611
- const message = err instanceof Error ? err.message : String(err);
46612
- return { error: `Sub-agent failed after 2 attempts: ${message}` };
46613
- }
46614
- }
46615
- return { error: "Sub-agent failed unexpectedly" };
46616
- }
46617
-
46618
46305
  // src/tools/provider-tool-helpers.ts
46619
46306
  function toolModelOutput(output) {
46620
46307
  if (output && typeof output === "object" && "analysis" in output) {
@@ -46629,6 +46316,7 @@ function buildAfterComplete(opts) {
46629
46316
  const { providerType, db: db2, memoryContext, collectedQueries } = opts;
46630
46317
  return (params) => {
46631
46318
  if (!db2 || !memoryContext) return;
46319
+ if (collectedQueries.length === 0) return;
46632
46320
  const sessionId = params.sessionId;
46633
46321
  if (sessionId) {
46634
46322
  try {
@@ -46830,73 +46518,20 @@ function sanitizeNrqlRows(rows) {
46830
46518
  }
46831
46519
 
46832
46520
  // src/lib/shared-prompts.ts
46833
- function buildOrchestratorPrompt(config2) {
46834
- return `You have access to ${config2.providerName} via the ${config2.toolName} tool. Pass it a complete task description \u2014 the sub-agent autonomously writes and executes ${config2.queryDescription}, handles errors, and returns analysis + raw results. May run concurrently with other provider tools.
46835
-
46836
- ## CRITICAL: Sub-Agent Is Stateless
46837
-
46838
- Every call to the ${config2.toolName} tool spawns a FRESH sub-agent with ZERO memory of previous calls. It does not see prior conversation history, previous queries, or earlier findings. You MUST treat each call as if you are talking to the sub-agent for the very first time.
46839
-
46840
- ### How to write a task
46841
- Every task you pass to the tool must be **completely self-contained** \u2014 a standalone brief that makes sense with zero outside context. The sub-agent cannot see your conversation history, previous tool results, or anything else. It receives ONLY the task string you write.
46521
+ 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.`;
46522
+ function buildUnifiedModePrompt(providerFragments, maxSteps) {
46523
+ return `${UNIFIED_ROLE_INTRO}
46842
46524
 
46843
- **Always include:**
46844
- - **Concrete identifiers** found so far: service names, error messages, trace IDs, metric values, timestamps, hostnames, endpoints \u2014 copy them verbatim
46845
- - **Environment**: prod / staging / dev \u2014 state it explicitly every time
46846
- - **What was already tried**: which queries ran, what they returned, what didn't work \u2014 so the sub-agent doesn't repeat failed approaches
46847
- - **What to investigate next**: the specific question this call should answer
46848
-
46849
- **Never do this:**
46850
- - "Check the upstream service" \u2014 WHICH service? The sub-agent doesn't know.
46851
- - "Look into those errors further" \u2014 WHICH errors? Repeat the error message and context.
46852
- - "Continue the investigation" \u2014 there is nothing to continue. Start from scratch with full context.
46853
-
46854
- Think of each task as a memo to a colleague who just joined the project and knows nothing about what you've done so far.
46855
-
46856
- ${config2.classifySection}
46857
-
46858
- ${config2.contextSection}
46859
-
46860
- ### Example
46861
- ${config2.example}
46862
-
46863
- ## After the Sub-Agent Returns
46864
-
46865
- Results are shown in a rich UI automatically. Do NOT repeat raw data tables. Instead, write a **developer-ready incident report** \u2014 your response must contain enough detail that a developer can read it, fully understand what happened, and act on it without asking follow-up questions.
46866
-
46867
- **Your response MUST include:**
46868
- 1. **The full story** \u2014 what happened, step by step, as a chronological narrative. Include the request chain/call flow with service names, endpoints, timestamps, durations, status codes, and exact error messages at each hop. Quote error messages verbatim.
46869
- 2. **Scope and impact** \u2014 is this isolated or systemic? How many requests affected? When did it start?
46870
- 3. **Root cause** \u2014 if identified, with the evidence chain. If not yet identified, say what's known and what's missing.
46871
-
46872
- **Do NOT suggest fixes, solutions, or recommendations.** Your job is to present the facts so the developer can decide what to do.
46873
-
46874
- **Every claim needs its proof.** Never state a fact without showing where it came from.
46875
-
46876
- **Never give a vague summary.** Be specific: exact error messages, counts, timestamps, service names.
46877
-
46878
- If the answer is partial, truncated, or needs deeper investigation \u2014 call the tool again. The next sub-agent starts completely blank: no memory, no context, no history. Your follow-up task must be a **complete standalone brief** containing:
46879
- 1. **All findings so far** \u2014 concrete values: metric numbers, service names, trace IDs, error messages, timestamps
46880
- 2. **What was already queried** \u2014 successful query patterns and filters, plus what failed and why
46881
- 3. **The specific next question** \u2014 what this new sub-agent should investigate
46882
-
46883
- If you don't carry forward findings, the next sub-agent will repeat the same queries and waste its step budget rediscovering what you already know.
46884
-
46885
- ## Detective Discipline: Track Every Lead
46525
+ ## Rules
46526
+ ${buildRules({ investigation: true })}
46886
46527
 
46887
- You are the detective stitching the case together across stateless sub-agents. Each sub-agent sees one piece; you see the whole board.
46528
+ ${DETECTIVE_MINDSET}
46888
46529
 
46889
- **After every sub-agent return:**
46890
- 1. **Catalog new identifiers** \u2014 extract every trace ID, service name, error class, endpoint, and timestamp from the sub-agent's response. Add them to your running list.
46891
- 2. **Check for uninvestigated leads** \u2014 compare your identifier list against what has been searched. Any identifier that was discovered but not yet queried in context is an open lead.
46892
- 3. **Never conclude with open leads** \u2014 if there are uninvestigated identifiers, call the tool again with a task that searches them. A conclusion built on partial evidence is wrong until proven otherwise.
46893
- 4. **Build the cross-call narrative** \u2014 sub-agents cannot connect their own findings to previous calls. You must synthesize: "Sub-agent 1 found error X in service A with traceId T. Sub-agent 2 traced T and found the root cause in service B." This stitching is YOUR job.
46530
+ ${EXECUTION_DISCIPLINE}
46894
46531
 
46895
- The task is **done** when it either:
46896
- - Answers the user's question with concrete data, OR
46897
- - Concludes it cannot be answered, citing specific evidence (e.g. "no events for this service", "field doesn't exist") \u2014 not vague "no data found."
46532
+ ${providerFragments.join("\n\n---\n\n")}
46898
46533
 
46899
- Never ask "Should I proceed?" \u2014 the user asked you to investigate, so investigate.`;
46534
+ ${buildAnalysisSection(maxSteps)}`;
46900
46535
  }
46901
46536
  function buildRules(opts) {
46902
46537
  const rules = [
@@ -46943,44 +46578,10 @@ For multi-step investigations:
46943
46578
  4. **\u2192 Can I answer now?** \u2014 If YES: respond. If NO: state what's missing.
46944
46579
 
46945
46580
  For simple questions (counts, lookups), skip this \u2014 just answer directly.`;
46946
- function buildExecutionLoop(exampleSteps) {
46947
- return `### Query Efficiency
46948
-
46949
- **Pack information per query.** Combine fields, use grouping to get breakdowns. One well-crafted query replaces three lazy ones.
46950
- **Empty results = wrong query, not missing data.** Check field names, time range, and filters. Pivot \u2014 don't retry the same approach.
46951
- **No speculation.** If a query returns empty or partial data, state what was searched and what was not found. Say "insufficient data to determine X" \u2014 never invent explanations for missing data.
46952
-
46953
- ### Execution Loop
46954
-
46955
- After writing the plan, repeat for each step:
46956
-
46957
- 1. **Step N: [Goal]** \u2014 state what gap this query fills
46958
- 2. **Tool call** \u2192 ONE query
46959
- 3. **\u2192 Found:** [concrete data] **\u2192 So what:** [one-sentence inference]
46960
- 4. **\u2192 Can I answer now?** \u2014 If YES: write final response immediately. If NO: state what's still missing and continue.
46961
-
46962
- ${exampleSteps}
46963
-
46964
- **STOP when "Done when" criteria are met.** Do NOT add "confirmation" or "completeness" queries.`;
46965
- }
46966
- function buildPlanFormat(scopeLabel, scopeDescription) {
46967
- return `### Plan format
46968
- \`\`\`
46969
- **${scopeLabel}:** ${scopeDescription}
46970
- **Reasoning:** [what's your most specific fact, hypothesis, approach]
46971
-
46972
- **Plan** (N steps):
46973
- 1. [Goal \u2014 what to find] \u2192 starting point
46974
- 2. [Goal \u2014 what to learn from step 1's results]
46975
- ...
46976
-
46977
- **Done when:** [success criteria]
46978
- \`\`\``;
46979
- }
46980
- function analysisBlock(marker26) {
46981
- const markerAction = marker26 === "text" ? "write `<analysis>` on its own line **before anything else**" : "call the `begin_analysis` tool **before writing anything**";
46982
- const markerStep = marker26 === "text" ? "`<analysis>` (on its own line \u2014 nothing before it except your investigation steps)" : "Call `begin_analysis` tool (nothing before it except your investigation steps)";
46983
- const markerRef = marker26 === "text" ? "this marker" : "this tool";
46581
+ function analysisBlock() {
46582
+ const markerAction = "call the `begin_analysis` tool **before writing anything**";
46583
+ const markerStep = "Call `begin_analysis` tool (nothing before it except your investigation steps)";
46584
+ const markerRef = "this tool";
46984
46585
  return `When you are ready to present your findings, ${markerAction}. Do NOT write any summary or findings before ${markerRef} \u2014 everything the user reads must come after it. The UI renders everything after it with distinct styling.
46985
46586
 
46986
46587
  ### Structure your response as:
@@ -47000,35 +46601,10 @@ function analysisBlock(marker26) {
47000
46601
  - Each tool call in the analysis should show different data from the others \u2014 different metric, different time slice, different service, or different grouping.
47001
46602
  - ${NO_FIXES_RULE}`;
47002
46603
  }
47003
- function buildFinalResponse(maxSteps) {
47004
- const wrapAt = maxSteps - 5;
47005
- return `## Final Response
47006
-
47007
- ${analysisBlock("text")}
47008
-
47009
- ## Step Budget
47010
-
47011
- You have a maximum of ${maxSteps} steps. If approaching step ${wrapAt}, wrap up immediately with what you have.`;
47012
- }
47013
- function buildDirectFinalResponse(maxSteps) {
47014
- return `## Response Format
47015
-
47016
- Your queries render visually in the UI \u2014 the user can see the results. Write a concise answer that references the visual results rather than repeating data.
47017
-
47018
- After your queries, write:
47019
- 1. **Answer**: Concrete answer with actual values \u2014 timestamps, names, counts, IDs. Include all relevant identifiers (service names, trace IDs, endpoints) so the orchestrator has them for follow-up.
47020
- 2. **Caveats** (optional): If the answer is incomplete, state what's missing and why. Omit if complete.
47021
-
47022
- Be specific: "Last login: 2024-01-15 14:30 UTC via payment-service" not "a recent login was found".
47023
-
47024
- ${NO_FIXES_RULE}
47025
-
47026
- You have a maximum of ${maxSteps} steps.`;
47027
- }
47028
46604
  function buildAnalysisSection(maxSteps) {
47029
46605
  return `## Response Format
47030
46606
 
47031
- ${analysisBlock("tool")}
46607
+ ${analysisBlock()}
47032
46608
  - For simple questions, the query results themselves are the visual evidence \u2014 just add a brief text answer.
47033
46609
 
47034
46610
  ## Step Budget
@@ -47041,42 +46617,19 @@ You have a maximum of ${maxSteps} steps. Most investigations should finish in 3-
47041
46617
  }
47042
46618
 
47043
46619
  // src/lib/prompt-builder.ts
47044
- function buildDirectSubAgentPrompt(config2) {
47045
- return `${config2.directRoleIntro}
47046
-
47047
- ${config2.authStopRule}
47048
-
47049
- ## Rules
47050
- ${buildRules({ investigation: false, extraRules: config2.extraRules })}
47051
-
47052
- ${config2.domainKnowledge}
47053
-
47054
- ${buildDirectFinalResponse(config2.subAgentMaxSteps)}
47055
-
47056
- Raw results are returned separately to the UI.`;
47057
- }
47058
- function buildInvestigateSubAgentPrompt(config2) {
46620
+ function buildUnifiedModeFragment(config2) {
47059
46621
  const extra = config2.extraSections?.length ? "\n\n" + config2.extraSections.join("\n\n") : "";
47060
- return `${config2.investigateRoleIntro}
46622
+ const rules = config2.extraRules?.length ? `
47061
46623
 
47062
- ${config2.authStopRule}
46624
+ ### ${config2.providerName} query rules
46625
+ ` + config2.extraRules.map((r, i) => `${i + 1}. ${r}`).join("\n") : "";
46626
+ return `# ${config2.providerName}
47063
46627
 
47064
- ## CRITICAL RULES
47065
- ${buildRules({ investigation: true, extraRules: config2.extraRules })}
47066
-
47067
- ${DETECTIVE_MINDSET}
46628
+ ${config2.authStopRule}
47068
46629
 
47069
46630
  ${config2.insideOutDebugging}
47070
46631
 
47071
- ${config2.planningPhase}
47072
-
47073
- ${buildExecutionLoop(config2.executionLoopExample)}
47074
-
47075
- ${config2.domainKnowledge}${extra}
47076
-
47077
- ${buildFinalResponse(config2.subAgentMaxSteps)}
47078
-
47079
- Raw results are returned separately to the UI.`;
46632
+ ${config2.domainKnowledge}${extra}${rules}`;
47080
46633
  }
47081
46634
  function buildDirectModePrompt(config2) {
47082
46635
  const extra = config2.extraSections?.length ? "\n\n" + config2.extraSections.join("\n\n") : "";
@@ -47105,76 +46658,11 @@ var NR_CONFIG = {
47105
46658
  authStopRule: NR_AUTH_STOP_RULE,
47106
46659
  domainKnowledge: NR_DOMAIN_KNOWLEDGE,
47107
46660
  insideOutDebugging: NR_INSIDE_OUT_DEBUGGING,
47108
- directRoleIntro: `You are a New Relic NRQL query specialist. Answer data lookups in 1-3 queries \u2014 counts, lists, metric checks. Do NOT investigate root causes.
47109
-
47110
- ## Approach
47111
- Answer exactly what was asked. Do NOT add unrelated analysis unless asked.
47112
- Pack information: use FACET for breakdowns, multiple SELECTs for multiple metrics \u2014 get the most from each query.
47113
- If initial results are sparse, try other event types (Transaction \u2192 TransactionError \u2192 Log) before concluding.
47114
- If your first 2 queries return empty, verify data exists: \`SELECT count(*) FROM Transaction SINCE 1 day ago\`.`,
47115
- investigateRoleIntro: `You are a New Relic incident investigator. 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. You trace root causes across event types, correlate signals, and build evidence chains.`,
47116
- planningPhase: `### Planning Phase
47117
- Before your first query, write a SHORT plan (2-4 sentences max). Think:
47118
- 1. **What's the most specific fact I have?** \u2014 Start there. Never start broad when you have something specific.
47119
- 2. **What's the minimum I need to answer?** \u2014 Usually: the error, the service, and whether it's isolated or systemic. That's often 2-3 queries, not 10.
47120
- 3. **When do I stop?** \u2014 Define your "done when" criteria BEFORE starting.
47121
-
47122
- Begin your FIRST response with the plan, followed by your first tool call.
47123
-
47124
- ${buildPlanFormat("Environment", "[prod/staging/dev]")}
47125
-
47126
- ### Example plan
47127
- \`\`\`
47128
- **Environment:** production
47129
- **Reasoning:** I have a concrete settlement ID (830189). Search TransactionError directly for it \u2014 probably in error.message or request.uri. If found, I have the error + context. Only expand to trace chain if the error alone doesn't explain the cause.
47130
-
47131
- **Plan** (3 steps):
47132
- 1. Search TransactionError for "830189" \u2014 get error details, appName, traceId
47133
- 2. If error is clear (e.g. validation failure) \u2192 done. If ambiguous (e.g. timeout) \u2192 trace the request chain via traceId
47134
- 3. Count similar errors to determine scope (isolated vs pattern)
47135
-
47136
- **Done when:** Error message, failing service, and whether it's isolated or systemic.
47137
- \`\`\``,
47138
- executionLoopExample: `**Step 1: Search TransactionError for ID 830189**
47139
- \u2192 call execute_nrql with: SELECT \\\`error.message\\\`, \\\`error.class\\\`, appName, traceId, timestamp FROM TransactionError WHERE \\\`error.message\\\` LIKE '%830189%' OR request.uri LIKE '%830189%' SINCE 24 hours ago LIMIT 10
47140
- **\u2192 Found:** 3 events: appName=payment-prod, error.class=SettlementException, error.message="pool exhausted, 50/50 connections", traceId=abc123, 14:28-14:31 UTC.
47141
- **\u2192 So what:** Connection pool exhaustion in payment-prod caused the settlement failure. The error is self-explanatory \u2014 no need to trace the request chain.
47142
- **\u2192 Can I answer now?** Almost \u2014 need to know if this is isolated or systemic.
47143
- **Step 2: Count similar errors to check scope**
47144
- \u2192 call execute_nrql with: SELECT count(*) FROM TransactionError WHERE appName = 'payment-prod' AND \\\`error.class\\\` = 'SettlementException' SINCE 1 hour ago TIMESERIES 5 minutes
47145
- **\u2192 Found:** 47 identical errors in last hour, all payment-prod, all "pool exhausted".
47146
- **\u2192 So what:** Systemic \u2014 not isolated to settlement 830189. Connection pool is saturated.
47147
- **\u2192 Can I answer now?** YES \u2014 switch to evidence presentation.`,
47148
46661
  directModeRoleIntro: `You are a New Relic expert having a direct conversation with a developer. You have full conversation history and can reference previous messages. Handle both simple lookups and multi-step investigations depending on what the user asks. 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.`,
47149
- subAgentMaxSteps: DEFAULTS.subAgentMaxSteps,
47150
46662
  directModeMaxSteps: DEFAULTS.directModeMaxSteps
47151
46663
  };
47152
- var directSubAgentPrompt = buildDirectSubAgentPrompt(NR_CONFIG);
47153
- var investigateSubAgentPrompt = buildInvestigateSubAgentPrompt(NR_CONFIG);
47154
46664
  var directModeSystemPrompt = buildDirectModePrompt(NR_CONFIG);
47155
- var newRelicSystemPrompt = buildOrchestratorPrompt({
47156
- providerName: "New Relic",
47157
- toolName: "nrql",
47158
- queryDescription: "NRQL queries",
47159
- classifySection: `## Crafting the Task
47160
-
47161
- ### 1. Classify and set directive
47162
- - **DIRECT** (lookup, count, metric check, "how many", "what is", "list"): \`directive="DIRECT"\`
47163
- - **INVESTIGATE** (root-cause, tracing, "why", "debug", "failing", cross-ref): \`directive="INVESTIGATE"\`
47164
- - **Ambiguous** ("something is slow"): Ask the user to narrow scope before calling the tool.
47165
-
47166
- If a DIRECT call returns insufficient results, re-call with \`directive="INVESTIGATE"\` and a more focused task.`,
47167
- contextSection: `### 2. Extract ALL context \u2014 the sub-agent only knows what you tell it
47168
- - **Environment**: prod/staging/dev \u2014 pass explicitly
47169
- - **Identifiers**: IDs, error messages, user names, URLs, trace IDs \u2014 verbatim
47170
- - **Service/endpoint**: if mentioned
47171
- - **Timeframe**: if stated`,
47172
- example: `User: "settlement endpoint failing in prod, settlement id 830189"
47173
- \u2192 nrql({ directive: "INVESTIGATE", task: "Find errors related to settlement ID 830189. Environment: production. Look in TransactionError for this ID in error.message or request.uri." })
47174
-
47175
- User: "how many errors in the last hour?"
47176
- \u2192 nrql({ directive: "DIRECT", task: "Count total errors in the last hour." })`
47177
- });
46665
+ var nrUnifiedFragment = buildUnifiedModeFragment(NR_CONFIG);
47178
46666
 
47179
46667
  // src/providers/newrelic/tools.ts
47180
46668
  function buildExecuteNrqlTool(provider, collectedQueries, writer) {
@@ -47215,42 +46703,6 @@ function createNewRelicDirectTools(provider, memoryContext, writer, db2) {
47215
46703
  afterComplete: buildAfterComplete({ providerType: "newrelic", db: db2, memoryContext, collectedQueries })
47216
46704
  };
47217
46705
  }
47218
- function createNewRelicTools(provider, memoryContext, writer, db2) {
47219
- return {
47220
- nrql: tool({
47221
- description: "Investigate New Relic data by describing WHAT you want to find out. A sub-agent will autonomously write and execute NRQL queries, handle error recovery, and save lessons learned. Results are AUTOMATICALLY displayed to the user in a rich UI \u2014 NEVER repeat or reformat them in your text response. If the sub-agent's analysis includes a question, use conversation context to answer it or ask the user.",
47222
- inputSchema: external_exports.object({
47223
- task: external_exports.string().describe("A clear description of what to investigate (e.g. 'find recent errors and their root causes', 'check latency for /api/users endpoint in the last hour')"),
47224
- directive: external_exports.enum(["DIRECT", "INVESTIGATE"]).describe(
47225
- "DIRECT for simple lookups/counts. INVESTIGATE for root-cause analysis, tracing, cross-referencing."
47226
- )
47227
- }),
47228
- execute: async ({ task, directive }, { toolCallId, abortSignal }) => {
47229
- if (!db2) {
47230
- return { error: "Database not available for model resolution." };
47231
- }
47232
- const collectedQueries = [];
47233
- const executeNrql = buildExecuteNrqlTool(provider, collectedQueries);
47234
- const systemPrompt = directive === "DIRECT" ? directSubAgentPrompt : investigateSubAgentPrompt;
47235
- return runSubAgent({
47236
- providerType: "newrelic",
47237
- db: db2,
47238
- systemPrompt,
47239
- task,
47240
- toolCallId,
47241
- queryTools: { execute_nrql: executeNrql },
47242
- queryToolNames: ["execute_nrql"],
47243
- collectedQueries,
47244
- memoryContext,
47245
- writer,
47246
- sessionId: writer?.sessionId,
47247
- abortSignal
47248
- });
47249
- },
47250
- toModelOutput: ({ output }) => subAgentModelOutput(output)
47251
- })
47252
- };
47253
- }
47254
46706
 
47255
46707
  // src/providers/newrelic/newrelic.provider.ts
47256
46708
  var NewRelicProvider = class extends BaseProvider {
@@ -47339,29 +46791,17 @@ var NewRelicProvider = class extends BaseProvider {
47339
46791
  return response.data?.actor.account.nrql.results ?? [];
47340
46792
  }
47341
46793
  getChatTools(options) {
47342
- if (options.mode === "direct") {
47343
- const direct = createNewRelicDirectTools(
47344
- this,
47345
- options.memoryContext,
47346
- options.writer,
47347
- options.db
47348
- );
47349
- return {
47350
- tools: direct.tools,
47351
- systemPrompt: direct.systemPrompt,
47352
- maxSteps: NR_DIRECT_MODE_MAX_STEPS,
47353
- afterComplete: direct.afterComplete
47354
- };
47355
- }
47356
- const tools = createNewRelicTools(
46794
+ const direct = createNewRelicDirectTools(
47357
46795
  this,
47358
46796
  options.memoryContext,
47359
46797
  options.writer,
47360
46798
  options.db
47361
46799
  );
47362
46800
  return {
47363
- tools,
47364
- promptFragments: [newRelicSystemPrompt]
46801
+ tools: direct.tools,
46802
+ maxSteps: NR_DIRECT_MODE_MAX_STEPS,
46803
+ afterComplete: direct.afterComplete,
46804
+ ...options.mode === "unified" ? { promptFragments: [nrUnifiedFragment] } : { systemPrompt: direct.systemPrompt }
47365
46805
  };
47366
46806
  }
47367
46807
  };
@@ -49926,188 +49366,6 @@ function deserializeMessage(line) {
49926
49366
  return JSONRPCMessageSchema2.parse(JSON.parse(line));
49927
49367
  }
49928
49368
 
49929
- // src/mcp/mcp-tools.ts
49930
- function extractMcpContent(result) {
49931
- if (result && typeof result === "object" && !Array.isArray(result)) {
49932
- const r = result;
49933
- if (Array.isArray(r.content)) {
49934
- const texts = r.content.filter((c) => c.type === "text" && typeof c.text === "string").map((c) => c.text);
49935
- if (texts.length > 0) {
49936
- const combined = texts.join("\n").trim();
49937
- try {
49938
- return JSON.parse(combined);
49939
- } catch {
49940
- return combined;
49941
- }
49942
- }
49943
- }
49944
- }
49945
- return result;
49946
- }
49947
- function detectTruncation(normalized) {
49948
- const str = typeof normalized === "string" ? normalized : JSON.stringify(normalized);
49949
- return str.includes("truncated due to") && str.includes("character limit");
49950
- }
49951
- function isTransportError(err) {
49952
- if (!(err instanceof Error)) return false;
49953
- const msg = err.message.toLowerCase();
49954
- return msg.includes("transport") || msg.includes("disconnected") || msg.includes("econnreset") || msg.includes("epipe") || msg.includes("channel closed") || msg.includes("process exited") || msg.includes("not connected") || msg.includes("econnrefused");
49955
- }
49956
- var MAX_MODEL_RESULT_CHARS = CONFIG.maxModelResultChars;
49957
- function buildModelResult(normalized) {
49958
- if (Array.isArray(normalized)) {
49959
- const total = normalized.length;
49960
- for (const count of [5, 3, 2, 1]) {
49961
- const sample = normalized.slice(0, count);
49962
- const payload = count < total ? { count: total, results: sample, note: `Showing ${count} of ${total}. Full results displayed in the UI.` } : { count: total, results: sample };
49963
- const json4 = JSON.stringify(payload);
49964
- if (json4.length <= MAX_MODEL_RESULT_CHARS) return json4;
49965
- }
49966
- return JSON.stringify({ count: total, note: "Results too large to include inline. Full results displayed in the UI." });
49967
- }
49968
- const json3 = typeof normalized === "string" ? normalized : JSON.stringify(normalized);
49969
- if (json3.length <= MAX_MODEL_RESULT_CHARS) return json3;
49970
- return json3.slice(0, MAX_MODEL_RESULT_CHARS) + `... [truncated \u2014 full results displayed in the UI]`;
49971
- }
49972
- function wrapMcpTools(mcpTools, provider) {
49973
- const collectedQueries = [];
49974
- const mcpToolNames = Object.keys(mcpTools);
49975
- const wrappedTools = {};
49976
- for (const [name26, mcpTool] of Object.entries(mcpTools)) {
49977
- const originalExecute = mcpTool.execute.bind(mcpTool);
49978
- wrappedTools[name26] = {
49979
- ...mcpTool,
49980
- execute: async (input, context2) => {
49981
- const queryStr = typeof input === "string" ? input : JSON.stringify(input).slice(0, 500);
49982
- const execArgs = context2?.abortSignal ? [input, { abortSignal: context2.abortSignal }] : [input];
49983
- try {
49984
- const result = await originalExecute(...execArgs);
49985
- const normalized = extractMcpContent(result);
49986
- if (detectTruncation(normalized)) {
49987
- const errorMsg = `The result exceeded the server's size limit and was discarded. To fix this:
49988
- 1. Reduce pageSize (use 5-10, never exceed 20)
49989
- 2. Add more specific filters to narrow results
49990
- 3. Request fewer fields or a shorter time range
49991
- Retry with a more targeted query.`;
49992
- collectedQueries.push({ query: `${name26}: ${queryStr}`, results: { error: errorMsg } });
49993
- return { content: [{ type: "text", text: errorMsg }] };
49994
- }
49995
- collectedQueries.push({ query: `${name26}: ${queryStr}`, results: normalized });
49996
- return { content: [{ type: "text", text: buildModelResult(normalized) }] };
49997
- } catch (err) {
49998
- const message = err instanceof Error ? err.message : String(err);
49999
- collectedQueries.push({ query: `${name26}: ${queryStr}`, results: { error: message } });
50000
- if (isTransportError(err)) {
50001
- provider.invalidateTools();
50002
- }
50003
- return { error: message };
50004
- }
50005
- }
50006
- };
50007
- }
50008
- return { wrappedTools, mcpToolNames, collectedQueries };
50009
- }
50010
- function createMcpChatTools(provider, definition, options) {
50011
- const { writer, memoryContext, db: db2 } = options;
50012
- const toolName = provider.type;
50013
- const orchestratorPrompt = buildOrchestratorPrompt({
50014
- providerName: definition.label,
50015
- toolName,
50016
- queryDescription: "MCP tool calls",
50017
- classifySection: `## Crafting the Task
50018
-
50019
- The sub-agent has access to ${definition.label} MCP tools. Describe WHAT you want to find out \u2014 the sub-agent will choose the right MCP tools autonomously.
50020
-
50021
- For simple lookups, be direct. For complex investigations, provide full context.`,
50022
- contextSection: `### Extract ALL context \u2014 the sub-agent only knows what you tell it
50023
- - **Environment**: prod/staging/dev \u2014 pass explicitly
50024
- - **Identifiers**: IDs, error messages, user names, URLs, trace IDs \u2014 verbatim
50025
- - **Service/endpoint**: if mentioned
50026
- - **Timeframe**: if stated`,
50027
- example: `User: "check recent errors in production"
50028
- \u2192 ${toolName}({ task: "Find recent errors in production. List error messages, counts, affected services, and timestamps." })
50029
-
50030
- User: "why is the checkout endpoint slow?"
50031
- \u2192 ${toolName}({ task: "Investigate latency issues on the checkout endpoint. Environment: production. Look for slow transactions, error patterns, and upstream dependencies." })`
50032
- });
50033
- const tools = {
50034
- [toolName]: tool({
50035
- description: `Investigate ${definition.label} data by describing WHAT you want to find out. A sub-agent will autonomously use MCP tools to query data, handle error recovery, and return analysis + raw results. Results are AUTOMATICALLY displayed to the user in a rich UI \u2014 NEVER repeat or reformat them in your text response.`,
50036
- inputSchema: external_exports.object({
50037
- task: external_exports.string().describe(
50038
- "A clear description of what to investigate \u2014 include all relevant context, identifiers, environment, and timeframe."
50039
- )
50040
- }),
50041
- execute: async ({ task }, { toolCallId, abortSignal }) => {
50042
- if (!db2) {
50043
- return { error: "Database not available for model resolution." };
50044
- }
50045
- let mcpTools;
50046
- try {
50047
- mcpTools = await provider.getMcpTools();
50048
- } catch (err) {
50049
- const msg = err instanceof Error ? err.message : String(err);
50050
- return { error: `Failed to discover MCP tools: ${msg}` };
50051
- }
50052
- if (!mcpTools || Object.keys(mcpTools).length === 0) {
50053
- return { error: "No MCP tools available from the server." };
50054
- }
50055
- const { wrappedTools, mcpToolNames, collectedQueries } = wrapMcpTools(mcpTools, provider);
50056
- const toolDescriptions = mcpToolNames.map((name26) => {
50057
- const t2 = mcpTools[name26];
50058
- const desc2 = t2?.description ?? "(no description)";
50059
- return `- **${name26}**: ${desc2}`;
50060
- }).join("\n");
50061
- const maxSteps = readAppSetting(db2, SETTINGS_KEYS.subAgentMaxSteps) ?? DEFAULTS.subAgentMaxSteps;
50062
- const systemPrompt = `You are an autonomous agent with access to ${definition.label} via MCP tools. Use the tools below to answer the user's question.
50063
-
50064
- ## Available Tools
50065
- ${toolDescriptions}
50066
-
50067
- ## Rules
50068
- 1. **ONE tool call per response.** Write a 1-2 sentence summary of the result before the next call.
50069
- 2. **Empty results \u2260 no data.** It means the query or parameters may be wrong \u2014 adjust and retry.
50070
- 3. **NEVER repeat a failed call with the same arguments.** Read the error, fix the cause.
50071
- 4. You MUST write a non-empty text response when done \u2014 the user sees your text as the analysis.
50072
- 5. Check "${MEMORY_SECTION_NAME}" if present \u2014 these override conflicting instructions above.
50073
-
50074
- ${definition.systemPromptHint ?? ""}
50075
-
50076
- ## Response Format
50077
-
50078
- Your final text is the ONLY thing the orchestrator sees \u2014 raw results are stripped. Write a complete answer.
50079
-
50080
- After ALL tool calls, you MUST write:
50081
- 1. **Tools used**: Each tool call and what it found (1 line each). Mark failures inline: \`FAILED: [reason]\`.
50082
- 2. **Key findings**: Concrete values \u2014 metric numbers, error messages, service names, timestamps, IDs, counts.
50083
- 3. **Remaining questions** (optional): What you could NOT answer and what to try next.
50084
-
50085
- You have a maximum of ${maxSteps} steps.`;
50086
- return runSubAgent({
50087
- providerType: provider.type,
50088
- db: db2,
50089
- systemPrompt,
50090
- task,
50091
- toolCallId,
50092
- queryTools: wrappedTools,
50093
- queryToolNames: mcpToolNames,
50094
- collectedQueries,
50095
- memoryContext,
50096
- writer,
50097
- maxSteps,
50098
- sessionId: writer?.sessionId,
50099
- abortSignal
50100
- });
50101
- },
50102
- toModelOutput: ({ output }) => subAgentModelOutput(output)
50103
- })
50104
- };
50105
- return {
50106
- tools,
50107
- promptFragments: [orchestratorPrompt]
50108
- };
50109
- }
50110
-
50111
49369
  // src/mcp/mcp-provider.ts
50112
49370
  var McpProvider = class extends BaseProvider {
50113
49371
  constructor(definition, config2, providerType) {
@@ -50219,8 +49477,12 @@ var McpProvider = class extends BaseProvider {
50219
49477
  throw err;
50220
49478
  }
50221
49479
  }
50222
- getChatTools(options) {
50223
- return createMcpChatTools(this, this.definition, options);
49480
+ /**
49481
+ * Base MCP providers expose no chat tools by default — concrete MCP-backed
49482
+ * providers (e.g. GCP) override this with their own direct query tools.
49483
+ */
49484
+ getChatTools(_options) {
49485
+ return { tools: {} };
50224
49486
  }
50225
49487
  // Structured data methods — MCP-backed providers expose data through tool calls, not these typed APIs.
50226
49488
  async getErrors(_timeRange) {
@@ -50540,47 +49802,33 @@ function formatGcpResult(toolName, data) {
50540
49802
  }
50541
49803
  }
50542
49804
  }
50543
- var MAX_MODEL_RESULT_CHARS2 = CONFIG.maxModelResultChars;
50544
- function buildGcpModelResult(toolName, normalized) {
50545
- const formatted = formatGcpResult(toolName, normalized);
50546
- if (formatted.length <= MAX_MODEL_RESULT_CHARS2) return formatted;
50547
- return formatted.slice(0, MAX_MODEL_RESULT_CHARS2) + "\n\n*[truncated \u2014 full results displayed in the UI]*";
50548
- }
50549
- function wrapGcpMcpTools(mcpTools, provider) {
50550
- const collectedQueries = [];
50551
- const mcpToolNames = Object.keys(mcpTools);
50552
- const wrappedTools = {};
50553
- for (const [name26, mcpTool] of Object.entries(mcpTools)) {
50554
- const originalExecute = mcpTool.execute.bind(mcpTool);
50555
- wrappedTools[name26] = {
50556
- ...mcpTool,
50557
- execute: async (input, context2) => {
50558
- const queryStr = typeof input === "string" ? input : JSON.stringify(input).slice(0, 500);
50559
- const execArgs = context2?.abortSignal ? [input, { abortSignal: context2.abortSignal }] : [input];
49805
+
49806
+ // src/mcp/mcp-tools.ts
49807
+ function extractMcpContent(result) {
49808
+ if (result && typeof result === "object" && !Array.isArray(result)) {
49809
+ const r = result;
49810
+ if (Array.isArray(r.content)) {
49811
+ const texts = r.content.filter((c) => c.type === "text" && typeof c.text === "string").map((c) => c.text);
49812
+ if (texts.length > 0) {
49813
+ const combined = texts.join("\n").trim();
50560
49814
  try {
50561
- const result = await originalExecute(...execArgs);
50562
- const normalized = extractMcpContent(result);
50563
- if (detectTruncation(normalized)) {
50564
- const errorMsg = `The result exceeded the server's size limit and was discarded. To fix this:
50565
- 1. Reduce pageSize (use 5-10, never exceed 20)
50566
- 2. Add more specific filters to narrow results
50567
- 3. Request fewer fields or a shorter time range
50568
- Retry with a more targeted query.`;
50569
- collectedQueries.push({ query: `${name26}: ${queryStr}`, results: { error: errorMsg } });
50570
- return { content: [{ type: "text", text: errorMsg }] };
50571
- }
50572
- collectedQueries.push({ query: `${name26}: ${queryStr}`, results: normalized });
50573
- return { content: [{ type: "text", text: buildGcpModelResult(name26, normalized) }] };
50574
- } catch (err) {
50575
- const message = err instanceof Error ? err.message : String(err);
50576
- collectedQueries.push({ query: `${name26}: ${queryStr}`, results: { error: message } });
50577
- if (isTransportError(err)) provider.invalidateTools();
50578
- return { error: message };
49815
+ return JSON.parse(combined);
49816
+ } catch {
49817
+ return combined;
50579
49818
  }
50580
49819
  }
50581
- };
49820
+ }
50582
49821
  }
50583
- return { wrappedTools, mcpToolNames, collectedQueries };
49822
+ return result;
49823
+ }
49824
+ function detectTruncation(normalized) {
49825
+ const str = typeof normalized === "string" ? normalized : JSON.stringify(normalized);
49826
+ return str.includes("truncated due to") && str.includes("character limit");
49827
+ }
49828
+ function isTransportError(err) {
49829
+ if (!(err instanceof Error)) return false;
49830
+ const msg = err.message.toLowerCase();
49831
+ return msg.includes("transport") || msg.includes("disconnected") || msg.includes("econnreset") || msg.includes("epipe") || msg.includes("channel closed") || msg.includes("process exited") || msg.includes("not connected") || msg.includes("econnrefused");
50584
49832
  }
50585
49833
 
50586
49834
  // src/providers/gcp/gcp-auth.ts
@@ -50676,50 +49924,9 @@ var GCP_CONFIG = {
50676
49924
  domainKnowledge: GCP_DOMAIN_KNOWLEDGE,
50677
49925
  insideOutDebugging: GCP_INSIDE_OUT_DEBUGGING,
50678
49926
  extraSections: [GCP_CROSS_SIGNAL],
50679
- directRoleIntro: `You are a Google Cloud observability expert. Answer the question completely and efficiently using the available GCP observability MCP tools (Cloud Logging, Cloud Monitoring, Cloud Trace, Error Reporting).`,
50680
- investigateRoleIntro: `You are a Google Cloud incident investigator. 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. You trace root causes across logs, metrics, traces, and error groups.`,
50681
- planningPhase: `### Planning Phase
50682
- Before your first tool call, write a SHORT plan. Think:
50683
- 1. **Which signal to start with?** \u2014 Logs (specific errors), Error Reporting (grouped patterns), Metrics (trends), Traces (latency)?
50684
- 2. **Most specific starting fact?** \u2014 A service name, error message, trace ID?
50685
- 3. **When do I stop?** \u2014 Define your "done when" criteria BEFORE starting.
50686
-
50687
- Begin your FIRST response with the plan, followed by your first tool call.
50688
-
50689
- ${buildPlanFormat("Scope", "[project/service/time range \u2014 determines query filtering]")}
50690
-
50691
- ### Example plan
50692
- \`\`\`
50693
- **Scope:** project=my-project-123, Cloud Run service=my-service
50694
- **Reasoning:** Service is crashing \u2014 check recent error logs first, then error groups for patterns, then OOM metrics.
50695
-
50696
- **Plan** (3 steps):
50697
- 1. Pull recent ERROR logs for the Cloud Run service
50698
- 2. Check Error Reporting for grouped crash patterns
50699
- 3. Query memory/CPU metrics to check for OOM
50700
-
50701
- **Done when:** Root cause of crashes identified (OOM, startup failure, dependency error, etc.).
50702
- \`\`\``,
50703
- executionLoopExample: `**Step 1: Pull recent error logs for the service**
50704
- \u2192 call list_log_entries with filter: resource.type="cloud_run_revision" AND resource.labels.service_name="my-service" AND severity>=ERROR
50705
- **\u2192 Found:** 47 ERROR entries in the last hour, top message "Container memory limit of 512Mi exceeded", service=my-service, revision=my-service-00042-abc.
50706
- **\u2192 So what:** The service is OOMKilling due to memory pressure.
50707
- **\u2192 Can I answer now?** Not yet \u2014 need to determine if this is a memory leak (gradual) or traffic spike (sudden).
50708
- **Step 2: Check memory metrics**
50709
- \u2192 call list_time_series with metric: run.googleapis.com/container/memory/utilizations
50710
- **\u2192 Found:** Memory usage climbed steadily from 60% to 100% over 3 hours before the first OOM.
50711
- **\u2192 So what:** Gradual memory increase suggests a leak, not a traffic spike.
50712
- **\u2192 Can I answer now?** YES \u2014 switch to evidence presentation.`,
50713
49927
  directModeRoleIntro: `You are a Google Cloud observability expert having a direct conversation with a developer. You have access to Cloud Logging, Cloud Monitoring, Cloud Trace, and Error Reporting via MCP tools. 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.`,
50714
- subAgentMaxSteps: DEFAULTS.subAgentMaxSteps,
50715
49928
  directModeMaxSteps: DEFAULTS.directModeMaxSteps
50716
49929
  };
50717
- var GCP_INVESTIGATE_CONFIG = {
50718
- ...GCP_CONFIG,
50719
- extraSections: [GCP_CROSS_SIGNAL, GCP_TOOL_REFERENCE]
50720
- };
50721
- var directSystemPrompt = buildDirectSubAgentPrompt(GCP_CONFIG);
50722
- var investigateSystemPrompt = buildInvestigateSubAgentPrompt(GCP_INVESTIGATE_CONFIG);
50723
49930
  var gcpDirectModeSystemPrompt = buildDirectModePrompt(GCP_CONFIG);
50724
49931
  function buildProjectConstraint(projectId) {
50725
49932
  if (!projectId) return "";
@@ -50729,85 +49936,11 @@ You MUST use ONLY project ID \`${projectId}\` for ALL tool calls.
50729
49936
  NEVER guess, infer, or try alternative project IDs. If a tool call fails for this project, report the error \u2014 do NOT retry with a different project name.
50730
49937
  `;
50731
49938
  }
50732
- var gcpSystemPrompt = buildOrchestratorPrompt({
50733
- providerName: "Google Cloud (GCP)",
50734
- toolName: "gcloud",
50735
- queryDescription: "GCP observability API calls",
50736
- classifySection: `## Crafting the Task
50737
-
50738
- ### 1. Classify and set directive
50739
- - **DIRECT** (fetch logs, list error groups, query a metric, "how many", "show me recent errors"): \`directive="DIRECT"\`
50740
- - **INVESTIGATE** (multi-step debugging, root-cause, cross-signal correlation, "why", "failing"): \`directive="INVESTIGATE"\`
50741
- - **Ambiguous** ("something is wrong"): Ask the user to narrow scope before calling the tool.
50742
-
50743
- If a DIRECT call returns insufficient results, re-call with \`directive="INVESTIGATE"\` and a more focused task.`,
50744
- contextSection: `### 2. Extract ALL context \u2014 the sub-agent only knows what you tell it
50745
- - **Project**: The GCP project ID is pre-configured in settings \u2014 the sub-agent already knows it. Do NOT ask the user for a project ID; omit it from your task description unless the user explicitly mentions a different one.
50746
- - **Service/resource**: Cloud Run service name, GKE cluster, function name, etc.
50747
- - **Identifiers**: error messages, trace IDs, log filter strings \u2014 verbatim
50748
- - **Timeframe**: if stated (e.g., "last hour", "since yesterday")`,
50749
- example: `User: "why is my Cloud Run service crashing in prod project my-project-123"
50750
- \u2192 gcloud({ directive: "INVESTIGATE", task: "Investigate why Cloud Run service is crashing. Project: my-project-123. Start by pulling recent ERROR logs for Cloud Run, then check error groups and any relevant metrics." })
50751
-
50752
- User: "show me recent errors"
50753
- \u2192 gcloud({ directive: "DIRECT", task: "List recent error log entries. Filter: severity>=ERROR, last 1 hour." })`
50754
- });
49939
+ function buildGcpUnifiedFragment(projectId) {
49940
+ return buildUnifiedModeFragment(GCP_CONFIG) + buildProjectConstraint(projectId);
49941
+ }
50755
49942
 
50756
49943
  // src/providers/gcp/tools.ts
50757
- function createGcpTools(provider, memoryContext, writer, db2, projectId) {
50758
- const projectConstraint = buildProjectConstraint(projectId);
50759
- const tools = {
50760
- gcloud: tool({
50761
- description: "Investigate Google Cloud observability data by describing WHAT you want to find out. A sub-agent will autonomously query Cloud Logging, Cloud Monitoring, Cloud Trace, and Error Reporting via MCP tools, handle errors, and return analysis + raw results. Results are AUTOMATICALLY displayed to the user in a rich UI \u2014 NEVER repeat or reformat them in your text response.",
50762
- inputSchema: external_exports.object({
50763
- task: external_exports.string().describe(
50764
- "A clear description of what to investigate (e.g. 'show recent errors for my Cloud Run service', 'investigate why error rate spiked in project my-project')"
50765
- ),
50766
- directive: external_exports.enum(["DIRECT", "INVESTIGATE"]).describe(
50767
- "DIRECT for simple lookups/lists/describes. INVESTIGATE for root-cause analysis, multi-step debugging."
50768
- )
50769
- }),
50770
- execute: async ({ task, directive }, { toolCallId, abortSignal }) => {
50771
- if (!db2) {
50772
- return { error: "Database not available for model resolution." };
50773
- }
50774
- const auth2 = await getGcpAuth();
50775
- if (!auth2.ok) return { error: auth2.message };
50776
- let mcpTools;
50777
- try {
50778
- mcpTools = await provider.getMcpTools();
50779
- } catch (err) {
50780
- const msg = err instanceof Error ? err.message : String(err);
50781
- return { error: `Failed to discover GCP MCP tools: ${msg}` };
50782
- }
50783
- if (!mcpTools || Object.keys(mcpTools).length === 0) {
50784
- return { error: "No GCP MCP tools available from the server." };
50785
- }
50786
- const { wrappedTools, mcpToolNames, collectedQueries } = wrapGcpMcpTools(mcpTools, provider);
50787
- const systemPrompt = (directive === "DIRECT" ? directSystemPrompt : investigateSystemPrompt) + projectConstraint;
50788
- return runSubAgent({
50789
- providerType: "gcp",
50790
- db: db2,
50791
- systemPrompt,
50792
- task,
50793
- toolCallId,
50794
- queryTools: wrappedTools,
50795
- queryToolNames: mcpToolNames,
50796
- collectedQueries,
50797
- memoryContext,
50798
- writer,
50799
- sessionId: writer?.sessionId,
50800
- abortSignal
50801
- });
50802
- },
50803
- toModelOutput: ({ output }) => subAgentModelOutput(output)
50804
- })
50805
- };
50806
- return {
50807
- tools,
50808
- promptFragments: [gcpSystemPrompt]
50809
- };
50810
- }
50811
49944
  function createGcpDirectTools(provider, memoryContext, writer, db2, projectId) {
50812
49945
  const projectConstraint = buildProjectConstraint(projectId);
50813
49946
  const rawMcpTools = provider.getCachedTools();
@@ -50887,22 +50020,299 @@ var GcpProvider = class extends McpProvider {
50887
50020
  return super.ping();
50888
50021
  }
50889
50022
  getChatTools(options) {
50890
- if (options.mode === "direct") {
50891
- const direct = createGcpDirectTools(
50892
- this,
50893
- options.memoryContext,
50894
- options.writer,
50895
- options.db,
50896
- this.config.projectId
50897
- );
50898
- return {
50899
- tools: direct.tools,
50900
- systemPrompt: direct.systemPrompt,
50901
- maxSteps: GCP_DIRECT_MODE_MAX_STEPS,
50902
- afterComplete: direct.afterComplete
50903
- };
50023
+ const direct = createGcpDirectTools(
50024
+ this,
50025
+ options.memoryContext,
50026
+ options.writer,
50027
+ options.db,
50028
+ this.config.projectId
50029
+ );
50030
+ const hasTools = Object.keys(direct.tools).length > 0;
50031
+ return {
50032
+ tools: direct.tools,
50033
+ maxSteps: GCP_DIRECT_MODE_MAX_STEPS,
50034
+ afterComplete: direct.afterComplete,
50035
+ ...options.mode === "unified" ? hasTools ? { promptFragments: [buildGcpUnifiedFragment(this.config.projectId)] } : {} : { systemPrompt: direct.systemPrompt }
50036
+ };
50037
+ }
50038
+ };
50039
+
50040
+ // src/providers/posthog/posthog.client.ts
50041
+ var DEFAULT_HOST = "https://us.posthog.com";
50042
+ var PosthogClient = class {
50043
+ apiKey;
50044
+ projectId;
50045
+ host;
50046
+ constructor(apiKey, projectId, host) {
50047
+ this.apiKey = apiKey;
50048
+ this.projectId = projectId;
50049
+ this.host = (host?.trim() || DEFAULT_HOST).replace(/\/+$/, "");
50050
+ }
50051
+ async query(hogql) {
50052
+ const response = await fetch(`${this.host}/api/projects/${this.projectId}/query/`, {
50053
+ method: "POST",
50054
+ headers: {
50055
+ "Content-Type": "application/json",
50056
+ Authorization: `Bearer ${this.apiKey}`
50057
+ },
50058
+ body: JSON.stringify({
50059
+ query: { kind: "HogQLQuery", query: hogql },
50060
+ name: "tracer"
50061
+ }),
50062
+ signal: AbortSignal.timeout(35e3)
50063
+ });
50064
+ if (!response.ok) {
50065
+ let detail = `${response.status} ${response.statusText}`;
50066
+ try {
50067
+ const body = await response.json();
50068
+ detail = body.detail ?? body.error ?? detail;
50069
+ } catch {
50070
+ }
50071
+ throw new Error(`PostHog query failed: ${detail}`);
50072
+ }
50073
+ const result = await response.json();
50074
+ if (result.error) {
50075
+ throw new Error(`PostHog query error: ${result.error}`);
50076
+ }
50077
+ return result;
50078
+ }
50079
+ };
50080
+
50081
+ // src/providers/posthog/posthog-formatter.ts
50082
+ function rowsToObjects(resp) {
50083
+ const { columns, results } = resp;
50084
+ if (!Array.isArray(results) || !Array.isArray(columns)) return [];
50085
+ return results.map((row) => {
50086
+ const obj = {};
50087
+ columns.forEach((col, i) => {
50088
+ obj[col] = row[i];
50089
+ });
50090
+ return obj;
50091
+ });
50092
+ }
50093
+ function fmtVal3(v) {
50094
+ if (v == null) return "";
50095
+ if (typeof v === "number") return Number.isInteger(v) ? String(v) : v.toFixed(2);
50096
+ if (Array.isArray(v)) return v.length <= 5 ? v.join("; ") : `[${v.length} items]`;
50097
+ if (typeof v === "object") return JSON.stringify(v);
50098
+ return String(v);
50099
+ }
50100
+ function csvEscape2(val) {
50101
+ if (val.includes(",") || val.includes('"') || val.includes("\n")) {
50102
+ return `"${val.replace(/"/g, '""')}"`;
50103
+ }
50104
+ return val;
50105
+ }
50106
+ function formatHogqlCsv(rows) {
50107
+ if (rows.length === 0) return "No results.";
50108
+ const headers = Object.keys(rows[0]);
50109
+ let displayRows = rows;
50110
+ let note = "";
50111
+ if (rows.length > 10) {
50112
+ displayRows = rows.slice(0, 10);
50113
+ note = `
50114
+ (${rows.length - 10} more rows omitted)`;
50115
+ }
50116
+ const hdr = headers.map(csvEscape2).join(",");
50117
+ const body = displayRows.map((r) => headers.map((k) => csvEscape2(fmtVal3(r[k]))).join(",")).join("\n");
50118
+ return `${hdr}
50119
+ ${body}${note}`;
50120
+ }
50121
+
50122
+ // src/providers/posthog/queries.ts
50123
+ function esc2(value) {
50124
+ return value.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
50125
+ }
50126
+ function timeExpr(value) {
50127
+ const v = value.trim().toLowerCase();
50128
+ if (v === "today") return "toStartOfDay(now())";
50129
+ if (v === "yesterday") return "toStartOfDay(now()) - interval 1 day";
50130
+ const rel = v.match(/^(\d+)\s+(second|minute|hour|day|week|month)s?\s+ago$/);
50131
+ if (rel) return `now() - interval ${rel[1]} ${rel[2]}`;
50132
+ if (/\d{4}-\d{2}-\d{2}/.test(value)) return `toDateTime('${esc2(value.trim())}')`;
50133
+ return "now() - interval 24 hour";
50134
+ }
50135
+ function timeFilter(since, until) {
50136
+ const sinceExpr = since ? timeExpr(since) : "now() - interval 24 hour";
50137
+ const untilClause = until ? ` AND timestamp <= ${timeExpr(until)}` : "";
50138
+ return ` AND timestamp >= ${sinceExpr}${untilClause}`;
50139
+ }
50140
+ function errorQuery2(since, until) {
50141
+ return `SELECT
50142
+ coalesce(properties.$exception_list[1].type, properties.$exception_type) AS error_class,
50143
+ coalesce(properties.$exception_list[1].value, properties.$exception_message) AS message,
50144
+ properties.$exception_fingerprint AS fingerprint,
50145
+ count() AS count,
50146
+ min(timestamp) AS first_seen,
50147
+ max(timestamp) AS last_seen
50148
+ FROM events
50149
+ WHERE event = '$exception'${timeFilter(since, until)}
50150
+ GROUP BY error_class, message, fingerprint
50151
+ ORDER BY count DESC
50152
+ LIMIT 100`;
50153
+ }
50154
+ function transactionQuery2(since, until) {
50155
+ return `SELECT
50156
+ coalesce(properties.$pathname, properties.$current_url) AS name,
50157
+ count() AS throughput
50158
+ FROM events
50159
+ WHERE event = '$pageview'${timeFilter(since, until)}
50160
+ GROUP BY name
50161
+ ORDER BY throughput DESC
50162
+ LIMIT 100`;
50163
+ }
50164
+ function logQuery2(since, filter2, until) {
50165
+ const filterClause = filter2 ? ` AND (event ILIKE '%${esc2(filter2)}%' OR properties.$current_url ILIKE '%${esc2(filter2)}%')` : "";
50166
+ return `SELECT timestamp, event, properties.$current_url AS url, distinct_id
50167
+ FROM events
50168
+ WHERE 1 = 1${timeFilter(since, until)}${filterClause}
50169
+ ORDER BY timestamp DESC
50170
+ LIMIT 200`;
50171
+ }
50172
+
50173
+ // src/providers/posthog/prompts.ts
50174
+ var POSTHOG_DIRECT_MODE_MAX_STEPS = DEFAULTS.directModeMaxSteps;
50175
+ var POSTHOG_CONFIG = {
50176
+ providerName: "PostHog",
50177
+ authStopRule: POSTHOG_AUTH_STOP_RULE,
50178
+ domainKnowledge: POSTHOG_DOMAIN_KNOWLEDGE,
50179
+ insideOutDebugging: POSTHOG_INSIDE_OUT_DEBUGGING,
50180
+ directModeRoleIntro: `You are a PostHog expert having a direct conversation with a developer. You have full conversation history and can reference previous messages. Handle both simple lookups and multi-step investigations depending on what the user asks. 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.`,
50181
+ directModeMaxSteps: DEFAULTS.directModeMaxSteps
50182
+ };
50183
+ var directModeSystemPrompt2 = buildDirectModePrompt(POSTHOG_CONFIG);
50184
+ var posthogUnifiedFragment = buildUnifiedModeFragment(POSTHOG_CONFIG);
50185
+
50186
+ // src/providers/posthog/tools.ts
50187
+ function buildExecuteHogqlTool(provider, collectedQueries, writer) {
50188
+ return tool({
50189
+ description: "Execute a HogQL query against PostHog.",
50190
+ inputSchema: external_exports.object({
50191
+ query: external_exports.string().describe("The HogQL query to execute")
50192
+ }),
50193
+ execute: async ({ query }, { toolCallId }) => {
50194
+ try {
50195
+ const rows = await provider.executeRawQuery(query);
50196
+ collectedQueries.push({ query, results: rows });
50197
+ writer?.write({
50198
+ type: "data-provider-part",
50199
+ data: { toolCallId, part: { type: "query", query, results: rows } }
50200
+ });
50201
+ const csv = formatHogqlCsv(rows);
50202
+ return { parts: [{ type: "query", query, results: rows }], analysis: csv };
50203
+ } catch (err) {
50204
+ const message = err instanceof Error ? err.message : String(err);
50205
+ collectedQueries.push({ query, results: { error: message } });
50206
+ return { error: message };
50207
+ }
50208
+ },
50209
+ toModelOutput: ({ output }) => toolModelOutput(output)
50210
+ });
50211
+ }
50212
+ function createPosthogDirectTools(provider, memoryContext, writer, db2) {
50213
+ const collectedQueries = [];
50214
+ return {
50215
+ tools: {
50216
+ execute_hogql: buildExecuteHogqlTool(provider, collectedQueries, writer),
50217
+ [ANALYSIS_TOOL_NAME]: beginAnalysisTool
50218
+ },
50219
+ systemPrompt: injectMemories(directModeSystemPrompt2, memoryContext),
50220
+ afterComplete: buildAfterComplete({ providerType: "posthog", db: db2, memoryContext, collectedQueries })
50221
+ };
50222
+ }
50223
+
50224
+ // src/providers/posthog/posthog.provider.ts
50225
+ var PosthogProvider = class extends BaseProvider {
50226
+ name = "posthog";
50227
+ type = "posthog";
50228
+ client;
50229
+ constructor(config2) {
50230
+ super();
50231
+ this.client = new PosthogClient(config2.apiKey, config2.projectId, config2.host);
50232
+ }
50233
+ async initialize() {
50234
+ await this.testConnection();
50235
+ }
50236
+ async testConnection() {
50237
+ try {
50238
+ await this.client.query("SELECT 1");
50239
+ this.connected = true;
50240
+ this.lastChecked = (/* @__PURE__ */ new Date()).toISOString();
50241
+ return true;
50242
+ } catch {
50243
+ this.connected = false;
50244
+ this.lastChecked = (/* @__PURE__ */ new Date()).toISOString();
50245
+ return false;
50904
50246
  }
50905
- return createGcpTools(this, options.memoryContext, options.writer, options.db, this.config.projectId);
50247
+ }
50248
+ async ping() {
50249
+ try {
50250
+ await this.client.query("SELECT 1");
50251
+ this.connected = true;
50252
+ this.lastChecked = (/* @__PURE__ */ new Date()).toISOString();
50253
+ return { ok: true };
50254
+ } catch (err) {
50255
+ this.connected = false;
50256
+ this.lastChecked = (/* @__PURE__ */ new Date()).toISOString();
50257
+ return { ok: false, error: err instanceof Error ? err.message : String(err) };
50258
+ }
50259
+ }
50260
+ async dispose() {
50261
+ this.connected = false;
50262
+ }
50263
+ async getErrors(timeRange) {
50264
+ const rows = rowsToObjects(await this.client.query(errorQuery2(timeRange.since, timeRange.until)));
50265
+ return rows.map((r, i) => ({
50266
+ id: `ph-err-${i}`,
50267
+ appName: "",
50268
+ // PostHog has no service/app dimension
50269
+ errorClass: String(r.error_class ?? "Unknown"),
50270
+ message: String(r.message ?? ""),
50271
+ count: Number(r.count ?? 0),
50272
+ firstSeen: String(r.first_seen ?? ""),
50273
+ lastSeen: String(r.last_seen ?? ""),
50274
+ transactionName: String(r.fingerprint ?? ""),
50275
+ // exception fingerprint (dedup key)
50276
+ provider: "posthog"
50277
+ }));
50278
+ }
50279
+ async getTransactions(timeRange) {
50280
+ const rows = rowsToObjects(await this.client.query(transactionQuery2(timeRange.since, timeRange.until)));
50281
+ return rows.map((r) => ({
50282
+ name: String(r.name ?? "Unknown"),
50283
+ avgDuration: 0,
50284
+ throughput: Number(r.throughput ?? 0),
50285
+ errorRate: 0,
50286
+ provider: "posthog"
50287
+ }));
50288
+ }
50289
+ async getLogs(timeRange, filter2) {
50290
+ const rows = rowsToObjects(await this.client.query(logQuery2(timeRange.since, filter2, timeRange.until)));
50291
+ return rows.map((r) => ({
50292
+ timestamp: String(r.timestamp ?? ""),
50293
+ level: "info",
50294
+ // PostHog events have no severity level
50295
+ message: String(r.event ?? ""),
50296
+ attributes: { url: r.url, distinct_id: r.distinct_id },
50297
+ provider: "posthog"
50298
+ }));
50299
+ }
50300
+ async executeRawQuery(query) {
50301
+ return rowsToObjects(await this.client.query(query));
50302
+ }
50303
+ getChatTools(options) {
50304
+ const direct = createPosthogDirectTools(
50305
+ this,
50306
+ options.memoryContext,
50307
+ options.writer,
50308
+ options.db
50309
+ );
50310
+ return {
50311
+ tools: direct.tools,
50312
+ maxSteps: POSTHOG_DIRECT_MODE_MAX_STEPS,
50313
+ afterComplete: direct.afterComplete,
50314
+ ...options.mode === "unified" ? { promptFragments: [posthogUnifiedFragment] } : { systemPrompt: direct.systemPrompt }
50315
+ };
50906
50316
  }
50907
50317
  };
50908
50318
 
@@ -50960,6 +50370,24 @@ function registerDefaultProviders(providers) {
50960
50370
  configFields: []
50961
50371
  }
50962
50372
  );
50373
+ providers.registerFactory(
50374
+ "posthog",
50375
+ (cfg) => new PosthogProvider({
50376
+ type: "posthog",
50377
+ apiKey: cfg.apiKey,
50378
+ projectId: cfg.projectId,
50379
+ host: cfg.host
50380
+ // default (us.posthog.com) applied in PosthogClient
50381
+ }),
50382
+ {
50383
+ label: "PostHog",
50384
+ configFields: [
50385
+ { key: "apiKey", label: "Personal API Key", type: "password" },
50386
+ { key: "projectId", label: "Project ID", type: "text" },
50387
+ { key: "host", label: "Host", type: "text", required: false }
50388
+ ]
50389
+ }
50390
+ );
50963
50391
  }
50964
50392
 
50965
50393
  // src/trpc/context.ts
@@ -56759,17 +56187,6 @@ var settingsRouter = router({
56759
56187
  writeAppSetting(ctx.db, SETTINGS_KEYS.chatModel, { provider: input.provider, modelId: input.modelId });
56760
56188
  return { success: true };
56761
56189
  }),
56762
- getChatMode: publicProcedure.query(({ ctx }) => {
56763
- if (!FEATURES.orchestratorMode) return DEFAULT_CHAT_MODE;
56764
- return readAppSetting(ctx.db, SETTINGS_KEYS.chatMode) ?? DEFAULT_CHAT_MODE;
56765
- }),
56766
- saveChatMode: publicProcedure.input(external_exports.enum(["orchestrator", "direct"])).mutation(({ ctx, input }) => {
56767
- if (!FEATURES.orchestratorMode && input === "orchestrator") {
56768
- return { success: false };
56769
- }
56770
- writeAppSetting(ctx.db, SETTINGS_KEYS.chatMode, input);
56771
- return { success: true };
56772
- }),
56773
56190
  getSubAgentModel: publicProcedure.input(external_exports.string().describe("Provider type, e.g. 'newrelic'")).query(({ ctx, input }) => {
56774
56191
  return readAppSetting(ctx.db, subAgentModelKey(input)) ?? null;
56775
56192
  }),
@@ -56975,6 +56392,7 @@ var AGENT_TYPE_LABELS = {
56975
56392
  chat: "Chat",
56976
56393
  newrelic: "New Relic sub-agent",
56977
56394
  gcp: "GCP sub-agent",
56395
+ posthog: "PostHog sub-agent",
56978
56396
  title: "Title gen",
56979
56397
  memory: "Memory"
56980
56398
  };
@@ -57633,6 +57051,24 @@ var StreamBroadcaster = class {
57633
57051
  }
57634
57052
  };
57635
57053
 
57054
+ // src/lib/current-context.ts
57055
+ function getCurrentDateBlock(db2) {
57056
+ const timezone = (db2 ? readAppSetting(db2, SETTINGS_KEYS.timezone) : null) ?? process.env.TRACER_TIMEZONE ?? DEFAULTS.timezone;
57057
+ const now2 = /* @__PURE__ */ new Date();
57058
+ const formatted = new Intl.DateTimeFormat("en-US", {
57059
+ timeZone: timezone,
57060
+ weekday: "long",
57061
+ year: "numeric",
57062
+ month: "long",
57063
+ day: "numeric",
57064
+ hour: "2-digit",
57065
+ minute: "2-digit",
57066
+ timeZoneName: "short"
57067
+ }).format(now2);
57068
+ return `## Current Date & Time
57069
+ ${formatted}`;
57070
+ }
57071
+
57636
57072
  // src/agents/base-agent.ts
57637
57073
  function sanitizeMessages(messages) {
57638
57074
  return messages.map((msg) => {
@@ -57688,7 +57124,7 @@ async function processLLMStream(sessionId, messages, context2, broadcaster, serv
57688
57124
 
57689
57125
  If a tool call fails, retry with a corrected approach. If you fail the same tool call twice, DO NOT retry again \u2014 stop and explain the issue to the user. Ask clarifying questions if needed. Never silently give up.
57690
57126
 
57691
- When the user's question spans multiple providers, call the relevant provider tools IN PARALLEL in the same step. Each runs independently with its own sub-agent. After all complete, synthesize findings across providers.`;
57127
+ When the user's question spans multiple providers, query each relevant provider and synthesize findings across the results.`;
57692
57128
  const fragments = collected.promptFragments ?? [];
57693
57129
  systemPrompt = fragments.length > 0 ? `${basePrompt}
57694
57130
 
@@ -57777,11 +57213,23 @@ No observability providers are currently configured. If the user asks about obse
57777
57213
  }
57778
57214
  });
57779
57215
  const reader = uiStream.getReader();
57216
+ let reasoningChars = 0;
57780
57217
  try {
57781
57218
  while (true) {
57782
57219
  const { done, value } = await reader.read();
57783
57220
  if (done) break;
57784
- const { providerMetadata: _, ...clean } = value;
57221
+ const v = value;
57222
+ if (v.type === "start-step") {
57223
+ reasoningChars = 0;
57224
+ } else if (v.type === "reasoning-delta" && typeof v.delta === "string") {
57225
+ reasoningChars += v.delta.length;
57226
+ if (reasoningChars > CONFIG.maxReasoningCharsPerStep) {
57227
+ console.warn(`[chat] reasoning exceeded ${CONFIG.maxReasoningCharsPerStep} chars in one step \u2014 aborting ${sessionId}`);
57228
+ serverAbort.abort();
57229
+ break;
57230
+ }
57231
+ }
57232
+ const { providerMetadata: _, ...clean } = v;
57785
57233
  broadcaster.emit(clean);
57786
57234
  }
57787
57235
  } catch (err) {
@@ -57890,7 +57338,15 @@ function collectBaseTools(registry2, db2, writer, mode, activeProvider) {
57890
57338
  }
57891
57339
  }
57892
57340
  }
57893
- const systemPrompt = systemPrompts.length > 0 ? systemPrompts.join("\n\n---\n\n") : void 0;
57341
+ const systemPrompt = mode === "unified" ? promptFragments.length > 0 ? injectMemories(
57342
+ buildUnifiedModePrompt(promptFragments, maxSteps ?? DEFAULTS.directModeMaxSteps),
57343
+ // Unified holds every connected provider's tools, so surface all their memories
57344
+ // (direct mode injects the active provider's memories via its own systemPrompt).
57345
+ {
57346
+ toolName: "unified",
57347
+ existingMemories: memories.filter((m) => connectedProviders.some((p) => p.type === m.toolName))
57348
+ }
57349
+ ) : void 0 : systemPrompts.length > 0 ? systemPrompts.join("\n\n---\n\n") : void 0;
57894
57350
  const afterComplete = afterCompleteCallbacks.length > 0 ? (params) => {
57895
57351
  for (const cb of afterCompleteCallbacks) cb(params);
57896
57352
  } : void 0;
@@ -57898,8 +57354,7 @@ function collectBaseTools(registry2, db2, writer, mode, activeProvider) {
57898
57354
  }
57899
57355
 
57900
57356
  // src/tools/chat-tools.ts
57901
- function collectChatTools(registry2, db2, writer, activeProvider) {
57902
- const mode = readAppSetting(db2, "chat_mode") ?? DEFAULT_CHAT_MODE;
57357
+ function collectChatTools(registry2, db2, writer, activeProvider, mode = DEFAULT_CHAT_MODE) {
57903
57358
  const { tools, promptFragments, systemPrompt, maxSteps, afterComplete, connectedProviders } = collectBaseTools(registry2, db2, writer, mode, activeProvider);
57904
57359
  if (connectedProviders.length === 0) {
57905
57360
  return { tools: void 0, promptFragments: [] };
@@ -58597,13 +58052,15 @@ function registerChatRoutes(app, context2) {
58597
58052
  generateSessionTitle(context2.db, id, textPart.text);
58598
58053
  }
58599
58054
  }
58600
- const mode = readAppSetting(context2.db, SETTINGS_KEYS.chatMode) ?? DEFAULT_CHAT_MODE;
58601
- const modelOverride = mode === "direct" && activeProvider ? resolveSubAgentModel(context2.db, activeProvider) : void 0;
58055
+ const isUnified = activeProvider === UNIFIED_SCOPE;
58056
+ const mode = isUnified ? "unified" : "direct";
58057
+ const scopedProvider = isUnified ? void 0 : activeProvider;
58058
+ const modelOverride = mode === "direct" && scopedProvider ? resolveSubAgentModel(context2.db, scopedProvider) : void 0;
58602
58059
  const result = await runChatAgent({
58603
58060
  sessionId: id,
58604
58061
  messages,
58605
58062
  context: context2,
58606
- collectTools: (writer) => collectChatTools(context2.providers, context2.db, writer, activeProvider),
58063
+ collectTools: (writer) => collectChatTools(context2.providers, context2.db, writer, scopedProvider, mode),
58607
58064
  sessionTitle: (updatedMessages) => {
58608
58065
  const firstUserMsg = updatedMessages.find((m) => m.role === "user");
58609
58066
  const textPart = firstUserMsg?.parts.find((p) => p.type === "text");