Skip to content

Commit c57b642

Browse files
committed
feat: separate system prompt tokens in context breakdown
Plugin now persists estimated system_prompt_tokens in session_meta during the system transform hook. Dashboard reads the stored value and shows System Prompt as a distinct purple segment in the token breakdown bar, separate from Conversation.
1 parent abcd2f4 commit c57b642

14 files changed

Lines changed: 72 additions & 11 deletions

File tree

packages/dashboard/src-tauri/src/db.rs

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -182,10 +182,11 @@ pub struct DreamStateEntry {
182182
#[derive(Debug, Serialize, Clone)]
183183
pub struct ContextTokenBreakdown {
184184
pub total_input_tokens: i64,
185+
pub system_prompt_tokens: i64,
185186
pub compartment_tokens: i64,
186187
pub fact_tokens: i64,
187188
pub memory_tokens: i64,
188-
pub conversation_tokens: i64, // total - compartments - facts - memories (includes system prompt)
189+
pub conversation_tokens: i64, // total - compartments - facts - memories - system_prompt
189190
pub compartment_count: i64,
190191
pub fact_count: i64,
191192
pub memory_count: i64,
@@ -203,14 +204,14 @@ pub fn get_context_token_breakdown(
203204
conn: &Connection,
204205
session_id: &str,
205206
) -> Result<Option<ContextTokenBreakdown>, rusqlite::Error> {
206-
// Get total input tokens from session_meta
207-
let total_input_tokens: i64 = conn
207+
// Get total input tokens and system prompt tokens from session_meta
208+
let (total_input_tokens, system_prompt_tokens): (i64, i64) = conn
208209
.query_row(
209-
"SELECT COALESCE(last_input_tokens, 0) FROM session_meta WHERE session_id = ?1",
210+
"SELECT COALESCE(last_input_tokens, 0), COALESCE(system_prompt_tokens, 0) FROM session_meta WHERE session_id = ?1",
210211
[session_id],
211-
|r| r.get(0),
212+
|r| Ok((r.get(0)?, r.get(1)?)),
212213
)
213-
.unwrap_or(0);
214+
.unwrap_or((0, 0));
214215

215216
// If no input tokens recorded, return None (no data available)
216217
if total_input_tokens == 0 {
@@ -257,8 +258,8 @@ pub fn get_context_token_breakdown(
257258
let fact_tokens = estimate_tokens(fact_chars);
258259
let memory_tokens = estimate_tokens(memory_chars);
259260

260-
// Conversation tokens = total - known sections (includes system prompt)
261-
let known_tokens = compartment_tokens + fact_tokens + memory_tokens;
261+
// Conversation tokens = total - all known sections
262+
let known_tokens = system_prompt_tokens + compartment_tokens + fact_tokens + memory_tokens;
262263
let conversation_tokens = if total_input_tokens > known_tokens {
263264
total_input_tokens - known_tokens
264265
} else {
@@ -267,6 +268,7 @@ pub fn get_context_token_breakdown(
267268

268269
Ok(Some(ContextTokenBreakdown {
269270
total_input_tokens,
271+
system_prompt_tokens,
270272
compartment_tokens,
271273
fact_tokens,
272274
memory_tokens,

packages/dashboard/src/components/SessionViewer/SessionViewer.tsx

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -390,13 +390,15 @@ export default function SessionViewer() {
390390
const hasData = () => total() > 0;
391391

392392
// Calculate percentages
393+
const systemPct = () => hasData() ? (data().system_prompt_tokens / total()) * 100 : 0;
393394
const compartmentPct = () => hasData() ? (data().compartment_tokens / total()) * 100 : 0;
394395
const factPct = () => hasData() ? (data().fact_tokens / total()) * 100 : 0;
395396
const memoryPct = () => hasData() ? (data().memory_tokens / total()) * 100 : 0;
396397
const conversationPct = () => hasData() ? (data().conversation_tokens / total()) * 100 : 0;
397398

398399
// Colors for each section
399400
const colors = {
401+
system: "#c084fc",
400402
compartments: "#4a9eff",
401403
facts: "#f0b429",
402404
memories: "#48bb78",
@@ -420,6 +422,21 @@ export default function SessionViewer() {
420422
overflow: "hidden",
421423
"margin-bottom": "20px",
422424
}}>
425+
<Show when={data().system_prompt_tokens > 0}>
426+
<div style={{
427+
width: `${systemPct()}%`,
428+
background: colors.system,
429+
display: "flex",
430+
"align-items": "center",
431+
"justify-content": "center",
432+
"font-size": "11px",
433+
"font-weight": "600",
434+
color: "#fff",
435+
"min-width": systemPct() > 8 ? "auto" : "0",
436+
}}>
437+
{systemPct() > 8 ? `${systemPct().toFixed(0)}%` : ""}
438+
</div>
439+
</Show>
423440
<Show when={data().compartment_tokens > 0}>
424441
<div style={{
425442
width: `${compartmentPct()}%`,
@@ -484,6 +501,25 @@ export default function SessionViewer() {
484501

485502
{/* Legend with details */}
486503
<div style={{ display: "flex", "flex-direction": "column", gap: "12px" }}>
504+
{/* System Prompt */}
505+
<div style={{ display: "flex", "align-items": "center", gap: "12px" }}>
506+
<div style={{
507+
width: "12px",
508+
height: "12px",
509+
"border-radius": "3px",
510+
background: colors.system,
511+
"flex-shrink": "0",
512+
}} />
513+
<div style={{ flex: 1, display: "flex", "justify-content": "space-between", "align-items": "center" }}>
514+
<span style={{ "font-size": "13px" }}>
515+
System Prompt
516+
</span>
517+
<span style={{ "font-size": "13px", "font-weight": "500", "font-family": "var(--font-mono)" }}>
518+
{data().system_prompt_tokens.toLocaleString()} <span style={{ color: "var(--text-muted)", "font-size": "12px" }}>({systemPct().toFixed(1)}%)</span>
519+
</span>
520+
</div>
521+
</div>
522+
487523
{/* Compartments */}
488524
<div style={{ display: "flex", "align-items": "center", gap: "12px" }}>
489525
<div style={{
@@ -552,7 +588,7 @@ export default function SessionViewer() {
552588
}} />
553589
<div style={{ flex: 1, display: "flex", "justify-content": "space-between", "align-items": "center" }}>
554590
<span style={{ "font-size": "13px" }}>
555-
Conversation + System
591+
Conversation
556592
</span>
557593
<span style={{ "font-size": "13px", "font-weight": "500", "font-family": "var(--font-mono)" }}>
558594
{data().conversation_tokens.toLocaleString()} <span style={{ color: "var(--text-muted)", "font-size": "12px" }}>({conversationPct().toFixed(1)}%)</span>

packages/dashboard/src/lib/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ export interface SessionMetaRow {
112112

113113
export interface ContextTokenBreakdown {
114114
total_input_tokens: number;
115+
system_prompt_tokens: number;
115116
compartment_tokens: number;
116117
fact_tokens: number;
117118
memory_tokens: number;

packages/plugin/src/features/magic-context/storage-db.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,7 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
263263
ensureColumn(db, "session_meta", "memory_block_count", "INTEGER DEFAULT 0");
264264
ensureColumn(db, "dream_queue", "retry_count", "INTEGER DEFAULT 0");
265265
ensureColumn(db, "tags", "reasoning_byte_size", "INTEGER DEFAULT 0");
266+
ensureColumn(db, "session_meta", "system_prompt_tokens", "INTEGER DEFAULT 0");
266267
}
267268

268269
function ensureColumn(db: Database, table: string, column: string, definition: string): void {

packages/plugin/src/features/magic-context/storage-meta-session.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import type { SessionMeta } from "./types";
1313
export function getOrCreateSessionMeta(db: Database, sessionId: string): SessionMeta {
1414
const result = db
1515
.prepare(
16-
"SELECT session_id, last_response_time, cache_ttl, counter, last_nudge_tokens, last_nudge_band, last_transform_error, is_subagent, last_context_percentage, last_input_tokens, times_execute_threshold_reached, compartment_in_progress, system_prompt_hash, cleared_reasoning_through_tag FROM session_meta WHERE session_id = ?",
16+
"SELECT session_id, last_response_time, cache_ttl, counter, last_nudge_tokens, last_nudge_band, last_transform_error, is_subagent, last_context_percentage, last_input_tokens, times_execute_threshold_reached, compartment_in_progress, system_prompt_hash, system_prompt_tokens, cleared_reasoning_through_tag FROM session_meta WHERE session_id = ?",
1717
)
1818
.get(sessionId);
1919

packages/plugin/src/features/magic-context/storage-meta-shared.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ export interface SessionMetaRow {
1717
// Intentional: type is string (MD5 hex digest), but the guard accepts string|number
1818
// for backward compatibility with pre-release DBs where the column was INTEGER.
1919
system_prompt_hash: string | number;
20+
system_prompt_tokens: number;
2021
cleared_reasoning_through_tag: number;
2122
}
2223

@@ -33,6 +34,7 @@ export const META_COLUMNS: Record<string, string> = {
3334
timesExecuteThresholdReached: "times_execute_threshold_reached",
3435
compartmentInProgress: "compartment_in_progress",
3536
systemPromptHash: "system_prompt_hash",
37+
systemPromptTokens: "system_prompt_tokens",
3638
clearedReasoningThroughTag: "cleared_reasoning_through_tag",
3739
};
3840

@@ -55,6 +57,7 @@ export function isSessionMetaRow(row: unknown): row is SessionMetaRow {
5557
typeof r.times_execute_threshold_reached === "number" &&
5658
typeof r.compartment_in_progress === "number" &&
5759
(typeof r.system_prompt_hash === "string" || typeof r.system_prompt_hash === "number") &&
60+
typeof r.system_prompt_tokens === "number" &&
5861
typeof r.cleared_reasoning_through_tag === "number"
5962
);
6063
}
@@ -74,6 +77,7 @@ export function getDefaultSessionMeta(sessionId: string): SessionMeta {
7477
timesExecuteThresholdReached: 0,
7578
compartmentInProgress: false,
7679
systemPromptHash: "",
80+
systemPromptTokens: 0,
7781
clearedReasoningThroughTag: 0,
7882
};
7983
}
@@ -120,6 +124,7 @@ export function toSessionMeta(row: SessionMetaRow): SessionMeta {
120124
timesExecuteThresholdReached: row.times_execute_threshold_reached,
121125
compartmentInProgress: row.compartment_in_progress === 1,
122126
systemPromptHash: String(row.system_prompt_hash),
127+
systemPromptTokens: row.system_prompt_tokens,
123128
clearedReasoningThroughTag: row.cleared_reasoning_through_tag,
124129
};
125130
}

packages/plugin/src/features/magic-context/storage-tags.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ function makeMemoryDatabase(): Database {
4747
times_execute_threshold_reached INTEGER DEFAULT 0,
4848
compartment_in_progress INTEGER DEFAULT 0,
4949
system_prompt_hash INTEGER DEFAULT 0,
50+
system_prompt_tokens INTEGER DEFAULT 0,
5051
cleared_reasoning_through_tag INTEGER DEFAULT 0
5152
);
5253
`);

packages/plugin/src/features/magic-context/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ export interface SessionMeta {
3030
timesExecuteThresholdReached: number;
3131
compartmentInProgress: boolean;
3232
systemPromptHash: string;
33+
systemPromptTokens: number;
3334
clearedReasoningThroughTag: number;
3435
}
3536

packages/plugin/src/hooks/magic-context/command-handler.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ function createTestDb(): Database {
6363
times_execute_threshold_reached INTEGER DEFAULT 0,
6464
compartment_in_progress INTEGER DEFAULT 0,
6565
system_prompt_hash INTEGER DEFAULT 0,
66+
system_prompt_tokens INTEGER DEFAULT 0,
6667
cleared_reasoning_through_tag INTEGER DEFAULT 0
6768
);
6869
`);

packages/plugin/src/hooks/magic-context/heuristic-cleanup.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ function makeMemoryDatabase(): Database {
4040
times_execute_threshold_reached INTEGER DEFAULT 0,
4141
compartment_in_progress INTEGER DEFAULT 0,
4242
system_prompt_hash INTEGER DEFAULT 0,
43+
system_prompt_tokens INTEGER DEFAULT 0,
4344
cleared_reasoning_through_tag INTEGER DEFAULT 0
4445
);
4546
`);

0 commit comments

Comments
 (0)