tracer-sh 0.2.4 → 0.2.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/packages/server/dist/index.js +389 -34
- package/packages/web/dist/assets/{SearchableSelect-CvsTUEdo.js → SearchableSelect-BPzDtKhB.js} +1 -1
- package/packages/web/dist/assets/{Settings-gjgo6xd4.js → Settings-ntDADPDR.js} +1 -1
- package/packages/web/dist/assets/{highlighted-body-OFNGDK62-DdqoooTx.js → highlighted-body-OFNGDK62-tPjSIaIA.js} +1 -1
- package/packages/web/dist/assets/index-B4Hwftt4.css +1 -0
- package/packages/web/dist/assets/index-IihLiR83.js +48 -0
- package/packages/web/dist/assets/index-NB5FNsou.js +14 -0
- package/packages/web/dist/assets/mermaid-GHXKKRXX-C9jh9K1i.js +191 -0
- package/packages/web/dist/index.html +2 -2
- package/packages/web/dist/assets/index-CxrnanDH.css +0 -1
- package/packages/web/dist/assets/index-DqPm6rps.js +0 -48
- package/packages/web/dist/assets/mermaid-GHXKKRXX-DQ_Kyq22.js +0 -204
|
@@ -16548,6 +16548,49 @@ var TOOL_NAMES = {
|
|
|
16548
16548
|
var CLIENT_TOOL_NAMES = Object.fromEntries(
|
|
16549
16549
|
Object.entries(TOOL_NAMES).map(([k, v]) => [k, `tool-${v}`])
|
|
16550
16550
|
);
|
|
16551
|
+
var ANALYSIS_MARKER = "<analysis>";
|
|
16552
|
+
function isAnalysisMessage(msg) {
|
|
16553
|
+
if (msg.role !== "assistant" || !msg.parts) return false;
|
|
16554
|
+
return msg.parts.some((p) => {
|
|
16555
|
+
if (p.type === CLIENT_TOOL_NAMES.BEGIN_ANALYSIS) return true;
|
|
16556
|
+
if (p.type !== "text") return false;
|
|
16557
|
+
const text3 = p.text;
|
|
16558
|
+
return typeof text3 === "string" && text3.includes(ANALYSIS_MARKER);
|
|
16559
|
+
});
|
|
16560
|
+
}
|
|
16561
|
+
function compactionUpTo(messages, boundaryIdx) {
|
|
16562
|
+
const boundary = messages[boundaryIdx];
|
|
16563
|
+
if (!boundary || boundary.role !== "assistant") return null;
|
|
16564
|
+
if (!isAnalysisMessage(boundary)) return boundaryIdx + 1;
|
|
16565
|
+
return boundaryIdx >= 1 ? boundaryIdx : null;
|
|
16566
|
+
}
|
|
16567
|
+
function findAnalysisMarker(parts) {
|
|
16568
|
+
const toolIdx = parts.findIndex((p) => p.type === CLIENT_TOOL_NAMES.BEGIN_ANALYSIS);
|
|
16569
|
+
if (toolIdx !== -1) return { kind: "tool", partIdx: toolIdx };
|
|
16570
|
+
for (let i = 0; i < parts.length; i++) {
|
|
16571
|
+
const p = parts[i];
|
|
16572
|
+
if (p.type !== "text") continue;
|
|
16573
|
+
const text3 = p.text;
|
|
16574
|
+
if (typeof text3 !== "string") continue;
|
|
16575
|
+
const idx = text3.indexOf(ANALYSIS_MARKER);
|
|
16576
|
+
if (idx !== -1) return { kind: "text", partIdx: i, charIdx: idx };
|
|
16577
|
+
}
|
|
16578
|
+
return null;
|
|
16579
|
+
}
|
|
16580
|
+
function splitAtAnalysis(parts) {
|
|
16581
|
+
const marker26 = findAnalysisMarker(parts);
|
|
16582
|
+
if (!marker26) return null;
|
|
16583
|
+
if (marker26.kind === "tool") {
|
|
16584
|
+
return { before: parts.slice(0, marker26.partIdx), analysis: parts.slice(marker26.partIdx + 1) };
|
|
16585
|
+
}
|
|
16586
|
+
const p = parts[marker26.partIdx];
|
|
16587
|
+
const beforeText = p.text.slice(0, marker26.charIdx);
|
|
16588
|
+
const afterText = p.text.slice(marker26.charIdx + ANALYSIS_MARKER.length);
|
|
16589
|
+
return {
|
|
16590
|
+
before: [...parts.slice(0, marker26.partIdx), ...beforeText.trim() ? [{ ...p, text: beforeText }] : []],
|
|
16591
|
+
analysis: [...afterText.trim() ? [{ ...p, text: afterText }] : [], ...parts.slice(marker26.partIdx + 1)]
|
|
16592
|
+
};
|
|
16593
|
+
}
|
|
16551
16594
|
var ImportedAnalysisSchema = external_exports.object({
|
|
16552
16595
|
v: external_exports.literal(1),
|
|
16553
16596
|
kind: external_exports.literal("analysis"),
|
|
@@ -16556,6 +16599,14 @@ var ImportedAnalysisSchema = external_exports.object({
|
|
|
16556
16599
|
parts: external_exports.array(external_exports.looseObject({ type: external_exports.string() })).max(200)
|
|
16557
16600
|
});
|
|
16558
16601
|
|
|
16602
|
+
// ../shared/src/feature-flags.ts
|
|
16603
|
+
var FEATURES = {
|
|
16604
|
+
/** Dashboard page with grid widgets. */
|
|
16605
|
+
dashboards: false,
|
|
16606
|
+
/** Monitor page with alerting. */
|
|
16607
|
+
monitors: false
|
|
16608
|
+
};
|
|
16609
|
+
|
|
16559
16610
|
// src/config.ts
|
|
16560
16611
|
var CONFIG = {
|
|
16561
16612
|
/** HTTP server port. Override with TRACER_PORT env var. */
|
|
@@ -20363,6 +20414,13 @@ var chatSessions = sqliteTable("chat_sessions", {
|
|
|
20363
20414
|
status: text("status").notNull().default("idle"),
|
|
20364
20415
|
/** Null for normal sessions. "imported" for sessions re-hydrated from a dropped analysis PNG. */
|
|
20365
20416
|
kind: text("kind"),
|
|
20417
|
+
/** Compaction: LLM-generated (possibly user-edited) summary of the first summaryUpTo messages. */
|
|
20418
|
+
summary: text("summary"),
|
|
20419
|
+
/** Count of leading messages covered by the summary. Index-based because
|
|
20420
|
+
* assistant messages can carry empty ids; prefixes are stable here (messages
|
|
20421
|
+
* are only appended or suffix-truncated, never reordered). */
|
|
20422
|
+
summaryUpTo: integer2("summary_up_to"),
|
|
20423
|
+
summaryCreatedAt: integer2("summary_created_at"),
|
|
20366
20424
|
createdAt: integer2("created_at").notNull().$defaultFn(() => unixNow()),
|
|
20367
20425
|
updatedAt: integer2("updated_at").notNull().$defaultFn(() => unixNow())
|
|
20368
20426
|
}, (t2) => [
|
|
@@ -20472,6 +20530,8 @@ chmodSync(TRACER_HOME, 448);
|
|
|
20472
20530
|
chmodSync(dataDir, 448);
|
|
20473
20531
|
var sqlite = new Database(join2(dataDir, "tracer.db"));
|
|
20474
20532
|
sqlite.pragma("journal_mode = WAL");
|
|
20533
|
+
sqlite.pragma("synchronous = NORMAL");
|
|
20534
|
+
sqlite.pragma("busy_timeout = 5000");
|
|
20475
20535
|
sqlite.pragma("foreign_keys = ON");
|
|
20476
20536
|
var db = drizzle(sqlite, { schema: schema_exports });
|
|
20477
20537
|
|
|
@@ -20504,6 +20564,9 @@ function runSetup() {
|
|
|
20504
20564
|
messages TEXT NOT NULL,
|
|
20505
20565
|
status TEXT NOT NULL DEFAULT 'idle',
|
|
20506
20566
|
kind TEXT,
|
|
20567
|
+
summary TEXT,
|
|
20568
|
+
summary_up_to INTEGER,
|
|
20569
|
+
summary_created_at INTEGER,
|
|
20507
20570
|
created_at INTEGER NOT NULL DEFAULT (unixepoch()),
|
|
20508
20571
|
updated_at INTEGER NOT NULL DEFAULT (unixepoch())
|
|
20509
20572
|
);
|
|
@@ -20607,7 +20670,12 @@ function runSetup() {
|
|
|
20607
20670
|
for (const ddl of [
|
|
20608
20671
|
`ALTER TABLE sub_agent_runs ADD COLUMN session_id TEXT`,
|
|
20609
20672
|
`ALTER TABLE tool_memories ADD COLUMN review_note TEXT`,
|
|
20610
|
-
`ALTER TABLE chat_sessions ADD COLUMN kind TEXT
|
|
20673
|
+
`ALTER TABLE chat_sessions ADD COLUMN kind TEXT`,
|
|
20674
|
+
`ALTER TABLE chat_sessions ADD COLUMN summary TEXT`,
|
|
20675
|
+
`ALTER TABLE chat_sessions ADD COLUMN summary_up_to INTEGER`,
|
|
20676
|
+
`ALTER TABLE chat_sessions ADD COLUMN summary_created_at INTEGER`,
|
|
20677
|
+
// Drops the short-lived id-based boundary column (never shipped in a release).
|
|
20678
|
+
`ALTER TABLE chat_sessions DROP COLUMN summary_up_to_id`
|
|
20611
20679
|
]) {
|
|
20612
20680
|
try {
|
|
20613
20681
|
sqlite.exec(ddl);
|
|
@@ -37930,7 +37998,7 @@ function injectMemories(prompt, memoryContext) {
|
|
|
37930
37998
|
const memoryBlock = `
|
|
37931
37999
|
|
|
37932
38000
|
## ${MEMORY_SECTION_NAME}
|
|
37933
|
-
These OVERRIDE any conflicting instructions above \u2014 they are
|
|
38001
|
+
These OVERRIDE any conflicting instructions above \u2014 they are lessons recorded from real failures in past sessions:
|
|
37934
38002
|
${lines.join("\n")}
|
|
37935
38003
|
`;
|
|
37936
38004
|
const firstBreak = prompt.indexOf("\n\n");
|
|
@@ -46223,6 +46291,8 @@ You MUST save a memory for every query failure:
|
|
|
46223
46291
|
- Wrong field names \u2192 "Don't use X, use Y for [purpose]"
|
|
46224
46292
|
- Wrong event types \u2192 "Don't query X for [goal], use Y instead"
|
|
46225
46293
|
|
|
46294
|
+
**Corrections must be demonstrated, not guessed.** Only write "use Y instead" if Y was actually run in this session and succeeded. If no working alternative was demonstrated, save only the mistake ("Don't use X in [context]") \u2014 a wrong correction is worse than none, because memories override future instructions.
|
|
46295
|
+
|
|
46226
46296
|
### From struggle patterns (evaluated)
|
|
46227
46297
|
Review the full session timeline for patterns where the agent struggled \u2014 multiple attempts with variations before finding the correct approach. Look for:
|
|
46228
46298
|
- Repeated EMPTY results with name/field/value variations followed by eventual success
|
|
@@ -46581,7 +46651,7 @@ function sanitizeNrqlRows(rows) {
|
|
|
46581
46651
|
}
|
|
46582
46652
|
|
|
46583
46653
|
// src/lib/shared-prompts.ts
|
|
46584
|
-
var UNIFIED_ROLE_INTRO = `You are Tracer, an observability expert in a direct conversation with a developer. You have DIRECT access to the query tools of multiple providers at once \u2014 each provider's syntax, fields, and debugging guidance are documented below. Pick the right provider(s) for each question; when a question spans providers, query them and correlate across the results in one investigation. You have full conversation history and can reference previous messages. You run as an AUTONOMOUS MULTI-STEP AGENT \u2014 after each tool call you automatically receive results and CAN (and often SHOULD) make additional tool calls before finishing.`;
|
|
46654
|
+
var UNIFIED_ROLE_INTRO = `You are Tracer, an observability expert in a direct conversation with a developer. You have DIRECT access to the query tools of multiple providers at once \u2014 each provider's syntax, common fields, and debugging guidance are documented below. Pick the right provider(s) for each question; when a question spans providers, query them and correlate across the results in one investigation. You have full conversation history and can reference previous messages. You run as an AUTONOMOUS MULTI-STEP AGENT \u2014 after each tool call you automatically receive results and CAN (and often SHOULD) make additional tool calls before finishing.`;
|
|
46585
46655
|
function buildUnifiedModePrompt(providerFragments, maxSteps) {
|
|
46586
46656
|
return `${UNIFIED_ROLE_INTRO}
|
|
46587
46657
|
|
|
@@ -46590,6 +46660,8 @@ ${buildRules({ investigation: true })}
|
|
|
46590
46660
|
|
|
46591
46661
|
${DETECTIVE_MINDSET}
|
|
46592
46662
|
|
|
46663
|
+
${EVIDENCE_GROUNDING}
|
|
46664
|
+
|
|
46593
46665
|
${EXECUTION_DISCIPLINE}
|
|
46594
46666
|
|
|
46595
46667
|
${providerFragments.join("\n\n---\n\n")}
|
|
@@ -46631,13 +46703,20 @@ You have limited steps. Every query must earn its place. Your goal is the **fast
|
|
|
46631
46703
|
3. **"Is there a single query that could answer multiple questions at once?"** \u2014 Combine work. Pack information density per query.
|
|
46632
46704
|
|
|
46633
46705
|
**"Good enough" beats "complete."** The user can always ask follow-up questions. Don't anticipate them \u2014 answer what was asked.`;
|
|
46706
|
+
var EVIDENCE_GROUNDING = `## Grounded in Evidence
|
|
46707
|
+
|
|
46708
|
+
Your only sources of truth are the literal text of tool results from this session, what the user has stated in the conversation, and what this prompt documents. Anything else is unknown \u2014 including the meaning of the data you retrieve.
|
|
46709
|
+
|
|
46710
|
+
1. **Field names and values are opaque labels.** Never translate or assign meaning to a field name, enum value, code, or flag beyond its literal text \u2014 systems attach internal meanings you cannot know. Report the raw value; if its meaning matters and is undocumented, say so.
|
|
46711
|
+
2. **Absence requires an empty probe.** Only claim something is missing, absent, or "not on file" if a query that would have returned it came back empty. Not having looked is not evidence of absence.
|
|
46712
|
+
3. **Separate facts, deductions, and gaps.** Facts restate query results. Deductions must follow from stated facts alone \u2014 present them as deductions and name the supporting results; correlation across results is not causation. Gaps are reported as "the data does not show X" \u2014 never filled with a plausible story.`;
|
|
46634
46713
|
var NO_FIXES_RULE = `**NEVER suggest fixes, remediation, next steps, or actions.** Forbidden phrasings include: "consider," "you should," "try," "might want to," "recommend," "could help," "suggests [action]," "would resolve," "to fix this." Any sentence about what to DO about the problem is forbidden, regardless of phrasing. Your job ends at "here is what happened and the evidence." The developer decides what to do.`;
|
|
46635
46714
|
var EXECUTION_DISCIPLINE = `## Execution Discipline
|
|
46636
46715
|
|
|
46637
46716
|
For multi-step investigations:
|
|
46638
46717
|
1. **Step N: [Goal]** \u2014 state what gap this fills
|
|
46639
46718
|
2. **Tool call** \u2192 ONE query
|
|
46640
|
-
3. **\u2192 Found:** [data] **\u2192 So what:** [
|
|
46719
|
+
3. **\u2192 Found:** [data] **\u2192 So what:** [only what this data supports \u2014 if it needs an assumption, it's a gap, not a finding]
|
|
46641
46720
|
4. **\u2192 Can I answer now?** \u2014 If YES: respond. If NO: state what's missing.
|
|
46642
46721
|
|
|
46643
46722
|
For simple questions (counts, lookups), skip this \u2014 just answer directly.`;
|
|
@@ -46651,6 +46730,7 @@ function analysisBlock() {
|
|
|
46651
46730
|
|
|
46652
46731
|
1. **Think first** \u2014 before writing anything, plan the evidence chain in your head:
|
|
46653
46732
|
- Known facts from query results, inferences that follow from them, and remaining gaps.
|
|
46733
|
+
- Self-audit each claim against the tool results already in this session: if no specific result backs it, drop it or present it explicitly as unverified. This is an in-head check \u2014 never run extra investigation queries for it.
|
|
46654
46734
|
- Which queries best VISUALIZE each finding \u2014 these become the tool calls you will run in this section.
|
|
46655
46735
|
- Do not start writing until you have a clear chain and a concrete list of visuals to run.
|
|
46656
46736
|
2. ${markerStep}
|
|
@@ -46676,7 +46756,7 @@ You have a maximum of ${maxSteps} steps. Most investigations should finish in 3-
|
|
|
46676
46756
|
|
|
46677
46757
|
## Final Reminders
|
|
46678
46758
|
- **Tool calls are the evidence.** Every substantive claim in your response needs a visual \u2014 even if the same query already ran during investigation, re-run it here. The analysis section must be self-contained.
|
|
46679
|
-
- **
|
|
46759
|
+
- **Stay Grounded in Evidence:** every claim maps to a specific tool result; values mean only what their literal text says; absence claims need an empty probe; gaps are stated as "the data does not show". No fixes.`;
|
|
46680
46760
|
}
|
|
46681
46761
|
|
|
46682
46762
|
// src/lib/prompt-builder.ts
|
|
@@ -46705,6 +46785,8 @@ ${buildRules({ investigation: true, extraRules: config2.extraRules })}
|
|
|
46705
46785
|
|
|
46706
46786
|
${DETECTIVE_MINDSET}
|
|
46707
46787
|
|
|
46788
|
+
${EVIDENCE_GROUNDING}
|
|
46789
|
+
|
|
46708
46790
|
${config2.insideOutDebugging}
|
|
46709
46791
|
|
|
46710
46792
|
${EXECUTION_DISCIPLINE}
|
|
@@ -56759,6 +56841,137 @@ var memoryRouter = router({
|
|
|
56759
56841
|
})
|
|
56760
56842
|
});
|
|
56761
56843
|
|
|
56844
|
+
// src/agents/utility/summary.ts
|
|
56845
|
+
var SUMMARY_SYSTEM_PROMPT = `You are compacting an AI debugging-assistant conversation into a detailed summary. Your summary will permanently REPLACE the original messages as the assistant's only memory of them, so anything you omit is lost forever. The assistant must be able to continue the investigation from your summary alone without redoing any completed work.
|
|
56846
|
+
|
|
56847
|
+
Write the summary as markdown with exactly these sections:
|
|
56848
|
+
|
|
56849
|
+
## Original intent
|
|
56850
|
+
What the user set out to do, in their own framing. Include later refinements or pivots of the goal.
|
|
56851
|
+
|
|
56852
|
+
## User requests & decisions
|
|
56853
|
+
Chronological list of every instruction, question, constraint, correction, and approval or rejection the user gave, and whether each was fulfilled. The assistant must be able to tell from this section alone what the user has and has not asked for.
|
|
56854
|
+
|
|
56855
|
+
## Investigation log
|
|
56856
|
+
Numbered, chronological. Each entry pairs an action with its result:
|
|
56857
|
+
1. One line on what was done and why, then the exact tool call, query, or API call (with its parameters and the time range it covered) in a fenced code block.
|
|
56858
|
+
|
|
56859
|
+
\u2192 Result: what it returned, with the key values verbatim. Render a result with more than one row (facets, top-N lists, table rows) as a markdown table; copy up to roughly 20 rows verbatim and note what was cut.
|
|
56860
|
+
|
|
56861
|
+
Never record an action without its result \u2014 an unpaired action forces the assistant to re-run it.
|
|
56862
|
+
|
|
56863
|
+
## Key findings & data (verbatim)
|
|
56864
|
+
The distilled facts the investigation established, with exact values copied verbatim \u2014 never paraphrase these:
|
|
56865
|
+
- IDs of any kind (trace IDs, entity GUIDs, account/project IDs, session IDs)
|
|
56866
|
+
- Exact error messages and stack-trace lines
|
|
56867
|
+
- File paths, service names, host names, URLs
|
|
56868
|
+
- Numbers: counts, rates, percentages, latencies, timestamps, time ranges
|
|
56869
|
+
Do not re-copy queries or result tables that already appear in the Investigation log \u2014 state the facts they established and name the log entry they came from.
|
|
56870
|
+
|
|
56871
|
+
## What did NOT work (dead ends)
|
|
56872
|
+
Approaches tried and abandoned, queries that errored or returned empty, hypotheses ruled out \u2014 and WHY each failed. This prevents the assistant from repeating them. If nothing failed, write "None."
|
|
56873
|
+
|
|
56874
|
+
## Conclusions & current state
|
|
56875
|
+
Each conclusion the assistant reached, stated together with the evidence supporting it, so it is never re-derived. What was communicated or delivered to the user (answers, recommendations, reports), and any artifacts produced.
|
|
56876
|
+
|
|
56877
|
+
## Open items
|
|
56878
|
+
Unresolved questions, pending next steps, anything the user asked for that has not been delivered yet. If none, write "None."
|
|
56879
|
+
|
|
56880
|
+
Rules:
|
|
56881
|
+
- Be detailed. Length is not a concern; losing information is. A long, precise summary is always better than a short, vague one.
|
|
56882
|
+
- Copy identifiers, queries, errors, and numbers character-for-character from the conversation.
|
|
56883
|
+
- Format for scanning: queries and commands go in fenced code blocks, multi-row results in markdown tables, and inline identifiers (service names, error classes, IDs, paths) in backticks.
|
|
56884
|
+
- State each piece of data in full exactly once, in the section where it belongs; later mentions reference it instead of repeating it.
|
|
56885
|
+
- Always pair what was run with what it returned.
|
|
56886
|
+
- If the conversation contains an analysis or post-mortem report (the begin_analysis tool marks where one starts), carry its content through verbatim in the relevant sections instead of re-summarizing it.
|
|
56887
|
+
- If an existing summary of older messages is provided, merge it with the new segment into ONE self-contained summary covering everything. Preserve all verbatim data from the existing summary unless the new segment explicitly supersedes it.
|
|
56888
|
+
- Do not add commentary, advice, or information that is not in the conversation.
|
|
56889
|
+
- Output only the summary markdown, nothing else.`;
|
|
56890
|
+
var TOOL_INPUT_CHAR_LIMIT = 2e3;
|
|
56891
|
+
var TOOL_OUTPUT_CHAR_LIMIT = 6e3;
|
|
56892
|
+
var GENERATION_TIMEOUT_MS = 5 * 6e4;
|
|
56893
|
+
function truncate(value, limit) {
|
|
56894
|
+
if (value === void 0) return "(none)";
|
|
56895
|
+
let text3;
|
|
56896
|
+
try {
|
|
56897
|
+
text3 = typeof value === "string" ? value : JSON.stringify(
|
|
56898
|
+
value,
|
|
56899
|
+
(_key, v) => typeof v === "string" && v.length > limit ? v.slice(0, limit) : v
|
|
56900
|
+
);
|
|
56901
|
+
} catch {
|
|
56902
|
+
text3 = String(value);
|
|
56903
|
+
}
|
|
56904
|
+
return text3.length > limit ? `${text3.slice(0, limit)}\u2026 (truncated)` : text3;
|
|
56905
|
+
}
|
|
56906
|
+
function serializeMessagesForSummary(messages) {
|
|
56907
|
+
const blocks = [];
|
|
56908
|
+
messages.forEach((msg, i) => {
|
|
56909
|
+
const lines = [`### Message ${i + 1} \u2014 ${msg.role}`];
|
|
56910
|
+
for (const part of msg.parts) {
|
|
56911
|
+
if (part.type === "text") {
|
|
56912
|
+
lines.push(part.text);
|
|
56913
|
+
continue;
|
|
56914
|
+
}
|
|
56915
|
+
if (part.type === "reasoning" || part.type === "step-start" || part.type.startsWith("data-") || part.type === "file") {
|
|
56916
|
+
continue;
|
|
56917
|
+
}
|
|
56918
|
+
const p = part;
|
|
56919
|
+
if (p.toolCallId) {
|
|
56920
|
+
const name26 = p.type.startsWith("tool-") ? p.type.slice(5) : p.type;
|
|
56921
|
+
const output = p.output !== void 0 ? p.output : p.errorText !== void 0 ? `error: ${p.errorText}` : void 0;
|
|
56922
|
+
lines.push(
|
|
56923
|
+
`[tool: ${name26}]`,
|
|
56924
|
+
`input: ${truncate(p.input, TOOL_INPUT_CHAR_LIMIT)}`,
|
|
56925
|
+
`output: ${truncate(output, TOOL_OUTPUT_CHAR_LIMIT)}`
|
|
56926
|
+
);
|
|
56927
|
+
}
|
|
56928
|
+
}
|
|
56929
|
+
blocks.push(lines.join("\n"));
|
|
56930
|
+
});
|
|
56931
|
+
return blocks.join("\n\n");
|
|
56932
|
+
}
|
|
56933
|
+
async function generateSessionSummary(db2, opts) {
|
|
56934
|
+
const resolved = resolveModel(db2);
|
|
56935
|
+
if ("error" in resolved) {
|
|
56936
|
+
console.warn("[summary] Cannot generate summary:", resolved.error);
|
|
56937
|
+
return { error: resolved.error, config: true };
|
|
56938
|
+
}
|
|
56939
|
+
const serialized = serializeMessagesForSummary(opts.messages);
|
|
56940
|
+
let userContent = opts.priorSummary ? `## Existing summary of older messages (merge into your output)
|
|
56941
|
+
${opts.priorSummary}
|
|
56942
|
+
|
|
56943
|
+
## New conversation segment to incorporate
|
|
56944
|
+
${serialized}` : `## Conversation to summarize
|
|
56945
|
+
${serialized}`;
|
|
56946
|
+
if (opts.keptAnalysis) {
|
|
56947
|
+
userContent += `
|
|
56948
|
+
|
|
56949
|
+
Note: the assistant's final analysis of the last exchange is preserved verbatim in the conversation right after your summary \u2014 record the work and results above without inventing or restating its conclusions.`;
|
|
56950
|
+
}
|
|
56951
|
+
try {
|
|
56952
|
+
const { text: text3, usage } = await generateText({
|
|
56953
|
+
model: resolved.model,
|
|
56954
|
+
temperature: 0,
|
|
56955
|
+
system: SUMMARY_SYSTEM_PROMPT,
|
|
56956
|
+
messages: [{ role: "user", content: userContent }],
|
|
56957
|
+
providerOptions: resolved.providerOptions,
|
|
56958
|
+
abortSignal: AbortSignal.timeout(GENERATION_TIMEOUT_MS)
|
|
56959
|
+
});
|
|
56960
|
+
const summary = text3.trim();
|
|
56961
|
+
if (!summary) return { error: "Summary generation returned no content" };
|
|
56962
|
+
recordAgentRun(db2, {
|
|
56963
|
+
sessionId: opts.sessionId,
|
|
56964
|
+
agentType: "summary",
|
|
56965
|
+
model: resolved.modelId,
|
|
56966
|
+
usage: extractUsage(usage, resolved.modelId)
|
|
56967
|
+
});
|
|
56968
|
+
return { summary };
|
|
56969
|
+
} catch (err) {
|
|
56970
|
+
console.warn("[summary] Failed to generate summary:", err);
|
|
56971
|
+
return { error: "Summary generation failed" };
|
|
56972
|
+
}
|
|
56973
|
+
}
|
|
56974
|
+
|
|
56762
56975
|
// src/trpc/routers/sessions.router.ts
|
|
56763
56976
|
var AGENT_TYPE_LABELS = {
|
|
56764
56977
|
chat: "Chat",
|
|
@@ -56766,7 +56979,8 @@ var AGENT_TYPE_LABELS = {
|
|
|
56766
56979
|
gcp: "GCP sub-agent",
|
|
56767
56980
|
posthog: "PostHog sub-agent",
|
|
56768
56981
|
title: "Title gen",
|
|
56769
|
-
memory: "Memory"
|
|
56982
|
+
memory: "Memory",
|
|
56983
|
+
summary: "Compaction"
|
|
56770
56984
|
};
|
|
56771
56985
|
var sessionsRouter = router({
|
|
56772
56986
|
list: publicProcedure.query(({ ctx }) => {
|
|
@@ -56801,7 +57015,10 @@ var sessionsRouter = router({
|
|
|
56801
57015
|
status: row.status,
|
|
56802
57016
|
kind: row.kind,
|
|
56803
57017
|
messages,
|
|
56804
|
-
updatedAt: row.updatedAt
|
|
57018
|
+
updatedAt: row.updatedAt,
|
|
57019
|
+
summary: row.summary,
|
|
57020
|
+
summaryUpTo: row.summaryUpTo,
|
|
57021
|
+
summaryCreatedAt: row.summaryCreatedAt
|
|
56805
57022
|
};
|
|
56806
57023
|
}),
|
|
56807
57024
|
getCost: publicProcedure.input(external_exports.object({ id: external_exports.string() })).query(({ ctx, input }) => {
|
|
@@ -56894,19 +57111,104 @@ var sessionsRouter = router({
|
|
|
56894
57111
|
return { id };
|
|
56895
57112
|
}),
|
|
56896
57113
|
truncateMessages: publicProcedure.input(external_exports.object({ id: external_exports.string(), keepCount: external_exports.number().int().min(0) })).mutation(({ ctx, input }) => {
|
|
56897
|
-
const row = ctx.db.select({ messages: chatSessions.messages }).from(chatSessions).where(eq(chatSessions.id, input.id)).get();
|
|
56898
|
-
if (!row) return { success: false };
|
|
57114
|
+
const row = ctx.db.select({ messages: chatSessions.messages, summaryUpTo: chatSessions.summaryUpTo }).from(chatSessions).where(eq(chatSessions.id, input.id)).get();
|
|
57115
|
+
if (!row) return { success: false, summaryCleared: false };
|
|
56899
57116
|
let messages = [];
|
|
56900
57117
|
try {
|
|
56901
57118
|
messages = JSON.parse(row.messages);
|
|
56902
57119
|
} catch {
|
|
56903
|
-
return { success: false };
|
|
57120
|
+
return { success: false, summaryCleared: false };
|
|
56904
57121
|
}
|
|
56905
57122
|
const truncated = messages.slice(0, input.keepCount);
|
|
57123
|
+
const keptAnalysisBoundary = row.summaryUpTo != null && isAnalysisMessage(messages[row.summaryUpTo] ?? { role: "" });
|
|
57124
|
+
const sourceUpTo = row.summaryUpTo != null ? row.summaryUpTo + (keptAnalysisBoundary ? 1 : 0) : 0;
|
|
57125
|
+
const summaryStale = row.summaryUpTo != null && input.keepCount < sourceUpTo;
|
|
56906
57126
|
ctx.db.update(chatSessions).set({
|
|
56907
57127
|
messages: JSON.stringify(truncated),
|
|
56908
|
-
updatedAt: unixNow()
|
|
57128
|
+
updatedAt: unixNow(),
|
|
57129
|
+
...summaryStale ? { summary: null, summaryUpTo: null, summaryCreatedAt: null } : {}
|
|
56909
57130
|
}).where(eq(chatSessions.id, input.id)).run();
|
|
57131
|
+
return { success: true, summaryCleared: summaryStale };
|
|
57132
|
+
}),
|
|
57133
|
+
compact: publicProcedure.input(external_exports.object({ id: external_exports.string(), upToIndex: external_exports.number().int().min(0) })).mutation(async ({ ctx, input }) => {
|
|
57134
|
+
const row = ctx.db.select().from(chatSessions).where(eq(chatSessions.id, input.id)).get();
|
|
57135
|
+
if (!row) throw new TRPCError({ code: "NOT_FOUND", message: "Session not found" });
|
|
57136
|
+
if (row.status === "streaming" || ctx.activeStreams.has(input.id)) {
|
|
57137
|
+
throw new TRPCError({ code: "CONFLICT", message: "Cannot compact while a response is in progress" });
|
|
57138
|
+
}
|
|
57139
|
+
let messages;
|
|
57140
|
+
try {
|
|
57141
|
+
messages = JSON.parse(row.messages);
|
|
57142
|
+
} catch {
|
|
57143
|
+
throw new TRPCError({ code: "INTERNAL_SERVER_ERROR", message: "Session messages are corrupted" });
|
|
57144
|
+
}
|
|
57145
|
+
const boundaryIdx = input.upToIndex;
|
|
57146
|
+
if (boundaryIdx >= messages.length) {
|
|
57147
|
+
throw new TRPCError({ code: "BAD_REQUEST", message: "Message not found in session" });
|
|
57148
|
+
}
|
|
57149
|
+
if (messages[boundaryIdx].role !== "assistant") {
|
|
57150
|
+
throw new TRPCError({ code: "BAD_REQUEST", message: "Can only compact up to an assistant message" });
|
|
57151
|
+
}
|
|
57152
|
+
const summaryUpTo = compactionUpTo(messages, boundaryIdx);
|
|
57153
|
+
if (summaryUpTo == null) {
|
|
57154
|
+
throw new TRPCError({ code: "BAD_REQUEST", message: "There are no messages to summarize before the analysis" });
|
|
57155
|
+
}
|
|
57156
|
+
const keptAnalysis = isAnalysisMessage(messages[boundaryIdx]);
|
|
57157
|
+
const incremental = !!row.summary && row.summaryUpTo != null && row.summaryUpTo < summaryUpTo;
|
|
57158
|
+
const segment = messages.slice(incremental ? row.summaryUpTo : 0, boundaryIdx + 1);
|
|
57159
|
+
if (incremental) {
|
|
57160
|
+
const head = segment[0];
|
|
57161
|
+
if (isAnalysisMessage(head)) {
|
|
57162
|
+
const split = splitAtAnalysis(head.parts);
|
|
57163
|
+
if (split) segment[0] = { ...head, parts: split.analysis };
|
|
57164
|
+
}
|
|
57165
|
+
}
|
|
57166
|
+
if (keptAnalysis) {
|
|
57167
|
+
const last = segment[segment.length - 1];
|
|
57168
|
+
const split = splitAtAnalysis(last.parts);
|
|
57169
|
+
if (split) segment[segment.length - 1] = { ...last, parts: split.before };
|
|
57170
|
+
}
|
|
57171
|
+
const result = await generateSessionSummary(ctx.db, {
|
|
57172
|
+
sessionId: input.id,
|
|
57173
|
+
priorSummary: incremental ? row.summary : void 0,
|
|
57174
|
+
messages: segment,
|
|
57175
|
+
keptAnalysis
|
|
57176
|
+
});
|
|
57177
|
+
if ("error" in result) {
|
|
57178
|
+
throw new TRPCError({
|
|
57179
|
+
code: result.config ? "PRECONDITION_FAILED" : "INTERNAL_SERVER_ERROR",
|
|
57180
|
+
message: result.error
|
|
57181
|
+
});
|
|
57182
|
+
}
|
|
57183
|
+
const fresh = ctx.db.select({ messages: chatSessions.messages, status: chatSessions.status }).from(chatSessions).where(eq(chatSessions.id, input.id)).get();
|
|
57184
|
+
const sourceUpTo = boundaryIdx + 1;
|
|
57185
|
+
let prefixUnchanged = false;
|
|
57186
|
+
if (fresh && fresh.status !== "streaming" && !ctx.activeStreams.has(input.id)) {
|
|
57187
|
+
try {
|
|
57188
|
+
const freshMessages = JSON.parse(fresh.messages);
|
|
57189
|
+
prefixUnchanged = JSON.stringify(freshMessages.slice(0, sourceUpTo)) === JSON.stringify(messages.slice(0, sourceUpTo));
|
|
57190
|
+
} catch {
|
|
57191
|
+
}
|
|
57192
|
+
}
|
|
57193
|
+
if (!prefixUnchanged) {
|
|
57194
|
+
throw new TRPCError({ code: "CONFLICT", message: "The conversation changed while the summary was being generated \u2014 try again" });
|
|
57195
|
+
}
|
|
57196
|
+
const summary = result.summary;
|
|
57197
|
+
const summaryCreatedAt = unixNow();
|
|
57198
|
+
ctx.db.update(chatSessions).set({ summary, summaryUpTo, summaryCreatedAt }).where(eq(chatSessions.id, input.id)).run();
|
|
57199
|
+
return { summary, summaryUpTo, summaryCreatedAt };
|
|
57200
|
+
}),
|
|
57201
|
+
updateSummary: publicProcedure.input(external_exports.object({ id: external_exports.string(), summary: external_exports.string().min(1) })).mutation(({ ctx, input }) => {
|
|
57202
|
+
const row = ctx.db.select({ summaryUpTo: chatSessions.summaryUpTo }).from(chatSessions).where(eq(chatSessions.id, input.id)).get();
|
|
57203
|
+
if (!row) throw new TRPCError({ code: "NOT_FOUND", message: "Session not found" });
|
|
57204
|
+
if (row.summaryUpTo == null) {
|
|
57205
|
+
throw new TRPCError({ code: "CONFLICT", message: "The summary no longer exists" });
|
|
57206
|
+
}
|
|
57207
|
+
ctx.db.update(chatSessions).set({ summary: input.summary }).where(eq(chatSessions.id, input.id)).run();
|
|
57208
|
+
return { success: true };
|
|
57209
|
+
}),
|
|
57210
|
+
clearSummary: publicProcedure.input(external_exports.object({ id: external_exports.string() })).mutation(({ ctx, input }) => {
|
|
57211
|
+
ctx.db.update(chatSessions).set({ summary: null, summaryUpTo: null, summaryCreatedAt: null }).where(eq(chatSessions.id, input.id)).run();
|
|
56910
57212
|
return { success: true };
|
|
56911
57213
|
})
|
|
56912
57214
|
});
|
|
@@ -57485,7 +57787,11 @@ function loadSessionMessages(db2, sessionId, newMessage) {
|
|
|
57485
57787
|
console.warn(`[chat] Corrupted session ${sessionId}, starting fresh`);
|
|
57486
57788
|
}
|
|
57487
57789
|
}
|
|
57488
|
-
return
|
|
57790
|
+
return {
|
|
57791
|
+
messages: [...sanitizeMessages(previous), newMessage],
|
|
57792
|
+
summary: existing?.summary ?? null,
|
|
57793
|
+
summaryUpTo: existing?.summaryUpTo ?? null
|
|
57794
|
+
};
|
|
57489
57795
|
}
|
|
57490
57796
|
function finalizeSession(sessionId, context2, broadcaster) {
|
|
57491
57797
|
if (!context2.activeStreams.has(sessionId)) return;
|
|
@@ -57493,7 +57799,7 @@ function finalizeSession(sessionId, context2, broadcaster) {
|
|
|
57493
57799
|
broadcaster.finish();
|
|
57494
57800
|
context2.activeStreams.delete(sessionId);
|
|
57495
57801
|
}
|
|
57496
|
-
async function processLLMStream(sessionId, messages, context2, broadcaster, serverAbort, collectTools, sessionTitle, model, modelId, providerOptions) {
|
|
57802
|
+
async function processLLMStream(sessionId, messages, context2, broadcaster, serverAbort, collectTools, sessionTitle, model, modelId, providerOptions, compaction) {
|
|
57497
57803
|
const writer = {
|
|
57498
57804
|
write: (part) => {
|
|
57499
57805
|
const p = part;
|
|
@@ -57504,7 +57810,29 @@ async function processLLMStream(sessionId, messages, context2, broadcaster, serv
|
|
|
57504
57810
|
};
|
|
57505
57811
|
const collected = collectTools(writer);
|
|
57506
57812
|
const tools = collected.tools;
|
|
57507
|
-
|
|
57813
|
+
let modelInput = messages;
|
|
57814
|
+
let summaryForPrompt = null;
|
|
57815
|
+
if (compaction.summary && compaction.summaryUpTo && compaction.summaryUpTo < messages.length) {
|
|
57816
|
+
const tail = messages.slice(compaction.summaryUpTo);
|
|
57817
|
+
if (tail[0].role === "user") {
|
|
57818
|
+
modelInput = tail;
|
|
57819
|
+
summaryForPrompt = compaction.summary;
|
|
57820
|
+
} else {
|
|
57821
|
+
const split = splitAtAnalysis(tail[0].parts);
|
|
57822
|
+
if (split && split.analysis.length > 0) {
|
|
57823
|
+
tail[0] = { ...tail[0], parts: split.analysis };
|
|
57824
|
+
modelInput = [
|
|
57825
|
+
{ id: "", role: "user", parts: [{ type: "text", text: "(The conversation up to this point was compacted into the summary in your instructions.)" }] },
|
|
57826
|
+
...tail
|
|
57827
|
+
];
|
|
57828
|
+
summaryForPrompt = compaction.summary;
|
|
57829
|
+
}
|
|
57830
|
+
}
|
|
57831
|
+
}
|
|
57832
|
+
if (compaction.summary && !summaryForPrompt) {
|
|
57833
|
+
console.warn(`[chat] Ignoring stale compaction boundary for ${sessionId} (summaryUpTo=${compaction.summaryUpTo}, messages=${messages.length})`);
|
|
57834
|
+
}
|
|
57835
|
+
const modelMessages = await convertToModelMessages(modelInput, {
|
|
57508
57836
|
tools,
|
|
57509
57837
|
convertDataPart: () => void 0
|
|
57510
57838
|
});
|
|
@@ -57525,6 +57853,16 @@ ${fragments.join("\n\n")}` : `${basePrompt}
|
|
|
57525
57853
|
No observability providers are currently configured. If the user asks about observability data, let them know they can connect providers in the Settings page.`;
|
|
57526
57854
|
}
|
|
57527
57855
|
systemPrompt += "\n\n" + getCurrentDateBlock(context2.db);
|
|
57856
|
+
if (summaryForPrompt) {
|
|
57857
|
+
systemPrompt += `
|
|
57858
|
+
|
|
57859
|
+
## Earlier conversation summary
|
|
57860
|
+
The earlier part of this conversation was compacted to save context. The summary below replaces those messages and is authoritative: the work it describes is already done \u2014 do NOT redo it. Reuse its recorded results, identifiers, queries, and conclusions.
|
|
57861
|
+
|
|
57862
|
+
<conversation_summary>
|
|
57863
|
+
${summaryForPrompt}
|
|
57864
|
+
</conversation_summary>`;
|
|
57865
|
+
}
|
|
57528
57866
|
const result = streamText({
|
|
57529
57867
|
model,
|
|
57530
57868
|
temperature: 0,
|
|
@@ -57559,6 +57897,7 @@ No observability providers are currently configured. If the user asks about obse
|
|
|
57559
57897
|
});
|
|
57560
57898
|
const title = sessionTitle(enrichedMessages);
|
|
57561
57899
|
const now2 = unixNow();
|
|
57900
|
+
const messagesJson = JSON.stringify(enrichedMessages);
|
|
57562
57901
|
recordAgentRun(context2.db, {
|
|
57563
57902
|
sessionId,
|
|
57564
57903
|
agentType: "chat",
|
|
@@ -57568,7 +57907,7 @@ No observability providers are currently configured. If the user asks about obse
|
|
|
57568
57907
|
context2.db.insert(chatSessions).values({
|
|
57569
57908
|
id: sessionId,
|
|
57570
57909
|
title,
|
|
57571
|
-
messages:
|
|
57910
|
+
messages: messagesJson,
|
|
57572
57911
|
status: "done",
|
|
57573
57912
|
createdAt: now2,
|
|
57574
57913
|
updatedAt: now2
|
|
@@ -57576,7 +57915,7 @@ No observability providers are currently configured. If the user asks about obse
|
|
|
57576
57915
|
target: chatSessions.id,
|
|
57577
57916
|
set: {
|
|
57578
57917
|
title: sql`CASE WHEN ${chatSessions.title} = ${DEFAULT_SESSION_TITLE} THEN ${title} ELSE ${chatSessions.title} END`,
|
|
57579
|
-
messages:
|
|
57918
|
+
messages: messagesJson,
|
|
57580
57919
|
status: sql`CASE WHEN ${chatSessions.status} = 'idle' THEN 'idle' ELSE 'done' END`,
|
|
57581
57920
|
updatedAt: now2
|
|
57582
57921
|
}
|
|
@@ -57637,7 +57976,7 @@ No observability providers are currently configured. If the user asks about obse
|
|
|
57637
57976
|
clearTimeout(timeoutId);
|
|
57638
57977
|
finalizeSession(sessionId, context2, broadcaster);
|
|
57639
57978
|
}
|
|
57640
|
-
async function runChatAgent({ sessionId, messages, context: context2, collectTools, sessionTitle, modelOverride }) {
|
|
57979
|
+
async function runChatAgent({ sessionId, messages, summary, summaryUpTo, context: context2, collectTools, sessionTitle, modelOverride }) {
|
|
57641
57980
|
const resolved = modelOverride ?? resolveModel(context2.db);
|
|
57642
57981
|
if ("error" in resolved) return { error: resolved.error };
|
|
57643
57982
|
const { model, modelId, providerOptions } = resolved;
|
|
@@ -57669,7 +58008,8 @@ async function runChatAgent({ sessionId, messages, context: context2, collectToo
|
|
|
57669
58008
|
sessionTitle,
|
|
57670
58009
|
model,
|
|
57671
58010
|
modelId,
|
|
57672
|
-
providerOptions
|
|
58011
|
+
providerOptions,
|
|
58012
|
+
{ summary, summaryUpTo }
|
|
57673
58013
|
).catch((err) => {
|
|
57674
58014
|
console.error(`[chat] Unhandled error in LLM processing for ${sessionId}:`, err);
|
|
57675
58015
|
finalizeSession(sessionId, context2, broadcaster);
|
|
@@ -57793,7 +58133,7 @@ function nextYPosition(db2, dashboardId) {
|
|
|
57793
58133
|
}
|
|
57794
58134
|
function collectDashboardTools(registry2, db2, writer, dashboardId) {
|
|
57795
58135
|
const dbId = dashboardId ?? "";
|
|
57796
|
-
const { tools, promptFragments, connectedProviders } = collectBaseTools(registry2, db2, writer);
|
|
58136
|
+
const { tools, promptFragments, connectedProviders } = collectBaseTools(registry2, db2, writer, "unified");
|
|
57797
58137
|
const defaultProvider = connectedProviders[0];
|
|
57798
58138
|
tools.create_widget = tool({
|
|
57799
58139
|
description: "Create a new dashboard widget. The query will be validated by executing it first. The widget auto-positions below existing widgets.",
|
|
@@ -57932,7 +58272,7 @@ The global date picker already shows the active time range, so titles should des
|
|
|
57932
58272
|
|
|
57933
58273
|
## Scope
|
|
57934
58274
|
You are managing widgets for the current dashboard only. The widget list above shows only this dashboard's widgets.`;
|
|
57935
|
-
const systemPrompt = [basePrompt, providerContext, widgetContext, ...promptFragments].join("\n\n");
|
|
58275
|
+
const systemPrompt = [basePrompt, EVIDENCE_GROUNDING, providerContext, widgetContext, ...promptFragments].join("\n\n");
|
|
57936
58276
|
return { tools, systemPrompt };
|
|
57937
58277
|
}
|
|
57938
58278
|
|
|
@@ -58239,7 +58579,7 @@ function getMonitorContext(db2) {
|
|
|
58239
58579
|
${lines.join("\n")}`;
|
|
58240
58580
|
}
|
|
58241
58581
|
function collectMonitorTools(registry2, db2, writer) {
|
|
58242
|
-
const { tools, promptFragments, connectedProviders } = collectBaseTools(registry2, db2, writer);
|
|
58582
|
+
const { tools, promptFragments, connectedProviders } = collectBaseTools(registry2, db2, writer, "unified");
|
|
58243
58583
|
const defaultProvider = connectedProviders[0];
|
|
58244
58584
|
tools.create_monitor = tool({
|
|
58245
58585
|
description: "Create a new monitor that periodically checks a query and alerts when a condition is met.",
|
|
@@ -58397,7 +58737,7 @@ The condition is a JS expression evaluated against the query \`result\` array. E
|
|
|
58397
58737
|
|
|
58398
58738
|
## Scope
|
|
58399
58739
|
You are managing all monitors. The monitor list above shows all existing monitors.`;
|
|
58400
|
-
const systemPrompt = [basePrompt, providerContext, monitorContext, ...promptFragments].join("\n\n");
|
|
58740
|
+
const systemPrompt = [basePrompt, EVIDENCE_GROUNDING, providerContext, monitorContext, ...promptFragments].join("\n\n");
|
|
58401
58741
|
return { tools, systemPrompt };
|
|
58402
58742
|
}
|
|
58403
58743
|
|
|
@@ -58437,7 +58777,7 @@ function generateSessionTitle(db2, sessionId, userMessage) {
|
|
|
58437
58777
|
function registerChatRoutes(app, context2) {
|
|
58438
58778
|
app.post("/api/chat", async (c) => {
|
|
58439
58779
|
const { id, message, activeProvider } = await c.req.json();
|
|
58440
|
-
const messages = loadSessionMessages(context2.db, id, message);
|
|
58780
|
+
const { messages, summary, summaryUpTo } = loadSessionMessages(context2.db, id, message);
|
|
58441
58781
|
if (messages.length === 1) {
|
|
58442
58782
|
const textPart = message.parts?.find((p) => p.type === "text");
|
|
58443
58783
|
if (textPart) {
|
|
@@ -58451,6 +58791,8 @@ function registerChatRoutes(app, context2) {
|
|
|
58451
58791
|
const result = await runChatAgent({
|
|
58452
58792
|
sessionId: id,
|
|
58453
58793
|
messages,
|
|
58794
|
+
summary,
|
|
58795
|
+
summaryUpTo,
|
|
58454
58796
|
context: context2,
|
|
58455
58797
|
collectTools: (writer) => collectChatTools(context2.providers, context2.db, writer, scopedProvider, mode),
|
|
58456
58798
|
sessionTitle: (updatedMessages) => {
|
|
@@ -58466,10 +58808,12 @@ function registerChatRoutes(app, context2) {
|
|
|
58466
58808
|
app.post("/api/dashboard-chat", async (c) => {
|
|
58467
58809
|
const { id, message, dashboardId } = await c.req.json();
|
|
58468
58810
|
const sessionId = dashboardSessionId(dashboardId);
|
|
58469
|
-
const messages = loadSessionMessages(context2.db, sessionId, message);
|
|
58811
|
+
const { messages, summary, summaryUpTo } = loadSessionMessages(context2.db, sessionId, message);
|
|
58470
58812
|
const result = await runChatAgent({
|
|
58471
58813
|
sessionId,
|
|
58472
58814
|
messages,
|
|
58815
|
+
summary,
|
|
58816
|
+
summaryUpTo,
|
|
58473
58817
|
context: context2,
|
|
58474
58818
|
collectTools: (writer) => collectDashboardTools(context2.providers, context2.db, writer, dashboardId),
|
|
58475
58819
|
sessionTitle: () => "Dashboard Builder"
|
|
@@ -58480,10 +58824,12 @@ function registerChatRoutes(app, context2) {
|
|
|
58480
58824
|
app.post("/api/monitor-chat", async (c) => {
|
|
58481
58825
|
const { message } = await c.req.json();
|
|
58482
58826
|
const sessionId = SESSION_PREFIX.MONITORS;
|
|
58483
|
-
const messages = loadSessionMessages(context2.db, sessionId, message);
|
|
58827
|
+
const { messages, summary, summaryUpTo } = loadSessionMessages(context2.db, sessionId, message);
|
|
58484
58828
|
const result = await runChatAgent({
|
|
58485
58829
|
sessionId,
|
|
58486
58830
|
messages,
|
|
58831
|
+
summary,
|
|
58832
|
+
summaryUpTo,
|
|
58487
58833
|
context: context2,
|
|
58488
58834
|
collectTools: (writer) => collectMonitorTools(context2.providers, context2.db, writer),
|
|
58489
58835
|
sessionTitle: () => "Monitor Builder"
|
|
@@ -58634,7 +58980,7 @@ function registerApiRoutes(app, context2) {
|
|
|
58634
58980
|
role: "user",
|
|
58635
58981
|
parts: [{ type: "text", text: message }]
|
|
58636
58982
|
};
|
|
58637
|
-
const messages = loadSessionMessages(context2.db, sessionId, userMessage);
|
|
58983
|
+
const { messages, summary, summaryUpTo } = loadSessionMessages(context2.db, sessionId, userMessage);
|
|
58638
58984
|
if (messages.length === 1) {
|
|
58639
58985
|
generateSessionTitle(context2.db, sessionId, message);
|
|
58640
58986
|
}
|
|
@@ -58647,6 +58993,8 @@ function registerApiRoutes(app, context2) {
|
|
|
58647
58993
|
const result = await runChatAgent({
|
|
58648
58994
|
sessionId,
|
|
58649
58995
|
messages,
|
|
58996
|
+
summary,
|
|
58997
|
+
summaryUpTo,
|
|
58650
58998
|
context: context2,
|
|
58651
58999
|
collectTools: (writer) => {
|
|
58652
59000
|
const collected = collectChatTools(context2.providers, context2.db, writer, scopedProvider, mode);
|
|
@@ -58731,24 +59079,31 @@ function mountStaticFiles(app) {
|
|
|
58731
59079
|
const webRoot = webCandidates.find((d) => existsSync(resolve3(d, "index.html")));
|
|
58732
59080
|
if (!webRoot) return;
|
|
58733
59081
|
const indexHtml = readFileSync4(resolve3(webRoot, "index.html"), "utf-8");
|
|
59082
|
+
const fileCache = /* @__PURE__ */ new Map();
|
|
58734
59083
|
app.use("*", async (c, next) => {
|
|
58735
59084
|
const reqPath = c.req.path.slice(1);
|
|
58736
59085
|
if (!reqPath) {
|
|
58737
59086
|
await next();
|
|
58738
59087
|
return;
|
|
58739
59088
|
}
|
|
58740
|
-
|
|
58741
|
-
if (
|
|
59089
|
+
let file2 = fileCache.get(reqPath);
|
|
59090
|
+
if (!file2) {
|
|
59091
|
+
const filePath = resolve3(webRoot, reqPath);
|
|
59092
|
+
if (!filePath.startsWith(webRoot) || !existsSync(filePath) || statSync(filePath).isDirectory()) {
|
|
59093
|
+
await next();
|
|
59094
|
+
return;
|
|
59095
|
+
}
|
|
58742
59096
|
const mime = MIME_TYPES[extname(filePath)] || "application/octet-stream";
|
|
58743
59097
|
const headers = { "Content-Type": mime };
|
|
58744
59098
|
if (reqPath.startsWith("assets/")) {
|
|
58745
59099
|
headers["Cache-Control"] = "public, max-age=31536000, immutable";
|
|
58746
59100
|
}
|
|
58747
|
-
|
|
59101
|
+
file2 = { body: readFileSync4(filePath), headers };
|
|
59102
|
+
fileCache.set(reqPath, file2);
|
|
58748
59103
|
}
|
|
58749
|
-
|
|
59104
|
+
return c.body(file2.body, { headers: file2.headers });
|
|
58750
59105
|
});
|
|
58751
|
-
app.get("*", (c) => c.html(indexHtml));
|
|
59106
|
+
app.get("*", (c) => c.html(indexHtml, 200, { "Cache-Control": "no-cache" }));
|
|
58752
59107
|
}
|
|
58753
59108
|
|
|
58754
59109
|
// src/http/app.ts
|
|
@@ -58892,8 +59247,8 @@ async function main() {
|
|
|
58892
59247
|
});
|
|
58893
59248
|
const context2 = createContext({ db, providers });
|
|
58894
59249
|
const app = createApp(context2);
|
|
58895
|
-
const scheduler = new MonitorScheduler(db, providers);
|
|
58896
|
-
scheduler
|
|
59250
|
+
const scheduler = FEATURES.monitors ? new MonitorScheduler(db, providers) : null;
|
|
59251
|
+
scheduler?.start();
|
|
58897
59252
|
const server = serve({ fetch: app.fetch, port: CONFIG.port, hostname: CONFIG.host }, (info) => {
|
|
58898
59253
|
console.log(`Tracer server running on http://localhost:${info.port}`);
|
|
58899
59254
|
});
|
|
@@ -58907,7 +59262,7 @@ Port ${CONFIG.port} is already in use. Run: lsof -ti :${CONFIG.port} | xargs kil
|
|
|
58907
59262
|
});
|
|
58908
59263
|
const shutdown = async (code = 0) => {
|
|
58909
59264
|
const timeout = setTimeout(() => process.exit(code === 0 ? 1 : code), CONFIG.shutdownGracePeriodMs);
|
|
58910
|
-
await scheduler
|
|
59265
|
+
await scheduler?.stop();
|
|
58911
59266
|
for (const p of providers.getAllProviders()) {
|
|
58912
59267
|
await p.dispose().catch(() => {
|
|
58913
59268
|
});
|