tracer-sh 0.1.1 → 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.
- package/package.json +2 -4
- package/packages/server/dist/chunk-ANVLQIEK.js +92 -0
- package/packages/server/dist/domain-knowledge-5JOEEXGN.js +11 -0
- package/packages/server/dist/index.js +454 -997
- package/packages/web/dist/assets/{SearchableSelect-CO0mH-rf.js → SearchableSelect-Dguq3D_c.js} +1 -1
- package/packages/web/dist/assets/Settings-BRNO62Ki.js +1 -0
- package/packages/web/dist/assets/{highlighted-body-OFNGDK62-ItGy-8Np.js → highlighted-body-OFNGDK62-C8dxSyKj.js} +1 -1
- package/packages/web/dist/assets/{index-BBsv6iUg.js → index-CFCII-7S.js} +12 -12
- package/packages/web/dist/assets/index-DLQaH2AJ.css +1 -0
- package/packages/web/dist/assets/{mermaid-GHXKKRXX-DMLLa87K.js → mermaid-GHXKKRXX-ga384H0G.js} +82 -82
- package/packages/web/dist/index.html +2 -2
- package/packages/web/dist/assets/Settings-BFymfzWO.js +0 -1
- package/packages/web/dist/assets/index-ckfqBqoi.css +0 -1
|
@@ -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-
|
|
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
|
-
|
|
46834
|
-
|
|
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
|
-
|
|
46844
|
-
|
|
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
|
-
|
|
46528
|
+
${DETECTIVE_MINDSET}
|
|
46888
46529
|
|
|
46889
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
46947
|
-
|
|
46948
|
-
|
|
46949
|
-
|
|
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(
|
|
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
|
|
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
|
-
|
|
46622
|
+
const rules = config2.extraRules?.length ? `
|
|
47061
46623
|
|
|
47062
|
-
${config2.
|
|
46624
|
+
### ${config2.providerName} query rules
|
|
46625
|
+
` + config2.extraRules.map((r, i) => `${i + 1}. ${r}`).join("\n") : "";
|
|
46626
|
+
return `# ${config2.providerName}
|
|
47063
46627
|
|
|
47064
|
-
|
|
47065
|
-
${buildRules({ investigation: true, extraRules: config2.extraRules })}
|
|
47066
|
-
|
|
47067
|
-
${DETECTIVE_MINDSET}
|
|
46628
|
+
${config2.authStopRule}
|
|
47068
46629
|
|
|
47069
46630
|
${config2.insideOutDebugging}
|
|
47070
46631
|
|
|
47071
|
-
${config2.
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
50223
|
-
|
|
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
|
-
|
|
50544
|
-
|
|
50545
|
-
|
|
50546
|
-
if (
|
|
50547
|
-
|
|
50548
|
-
|
|
50549
|
-
|
|
50550
|
-
|
|
50551
|
-
|
|
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
|
-
|
|
50562
|
-
|
|
50563
|
-
|
|
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
|
|
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
|
-
|
|
50733
|
-
|
|
50734
|
-
|
|
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
|
-
|
|
50891
|
-
|
|
50892
|
-
|
|
50893
|
-
|
|
50894
|
-
|
|
50895
|
-
|
|
50896
|
-
|
|
50897
|
-
|
|
50898
|
-
|
|
50899
|
-
|
|
50900
|
-
|
|
50901
|
-
|
|
50902
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
|
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 =
|
|
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
|
|
58601
|
-
const
|
|
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,
|
|
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");
|