@cydm/pie 2.6.11 → 2.6.12

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/dist/cli.js CHANGED
@@ -7,7 +7,7 @@ import {
7
7
  createCliWebResearchHealthStore,
8
8
  formatSize,
9
9
  truncateHeadTail
10
- } from "./chunks/chunk-MA4CHFN4.js";
10
+ } from "./chunks/chunk-AAEBH6KX.js";
11
11
  import {
12
12
  AGENTS_CONTEXT_FILE_NAME,
13
13
  AgentSessionController,
@@ -63,8 +63,8 @@ import {
63
63
  setExtensionEnabledInSettings,
64
64
  shouldPreserveExecutionStateForUserText,
65
65
  supersedeExecutionState
66
- } from "./chunks/chunk-ZVM234WE.js";
67
- import "./chunks/chunk-PAC62XU4.js";
66
+ } from "./chunks/chunk-PPEWRUMQ.js";
67
+ import "./chunks/chunk-HHNOENEH.js";
68
68
  import {
69
69
  Deref,
70
70
  EMPTY_ASSISTANT_CONTENT_ERROR,
@@ -70171,6 +70171,68 @@ var SessionTraceWriter = class extends SessionTraceSnapshotWriter {
70171
70171
  sessionManager;
70172
70172
  };
70173
70173
 
70174
+ // src/runtime/context-signals.ts
70175
+ import { createHash as createHash4 } from "crypto";
70176
+ function applyContextSignalsToFooter(target) {
70177
+ if (!target.agent?.state) return;
70178
+ const signals = computeContextSignals(
70179
+ target,
70180
+ target.agent.state.systemPrompt ?? "",
70181
+ target.agent.state.tools,
70182
+ target.sessionManager.getMessages()
70183
+ );
70184
+ target.footer.setState({
70185
+ prefixHash: signals.prefixHash,
70186
+ prefixHashChanged: signals.prefixHashChanged,
70187
+ cacheHitRate: signals.cacheHitRate
70188
+ });
70189
+ }
70190
+ function computePromptPrefixHash(systemPrompt, tools) {
70191
+ return createHash4("sha256").update(
70192
+ JSON.stringify({
70193
+ systemPrompt,
70194
+ tools: tools.map((tool) => ({
70195
+ name: tool.name,
70196
+ description: tool.description ?? "",
70197
+ parameters: tool.parameters ?? null
70198
+ }))
70199
+ })
70200
+ ).digest("hex").slice(0, 8);
70201
+ }
70202
+ function calculateCumulativeCacheHitRate(messages) {
70203
+ let input = 0;
70204
+ let cacheRead = 0;
70205
+ for (const message of messages) {
70206
+ if (message.role !== "assistant") continue;
70207
+ const usage = message.usage;
70208
+ if (!usage) continue;
70209
+ input += usage.input;
70210
+ cacheRead += usage.cacheRead;
70211
+ }
70212
+ const base = input + cacheRead;
70213
+ return base > 0 ? cacheRead / base : null;
70214
+ }
70215
+ var signalMemories = /* @__PURE__ */ new WeakMap();
70216
+ var lastSignals = /* @__PURE__ */ new WeakMap();
70217
+ function getLastContextSignals(hostKey) {
70218
+ return lastSignals.get(hostKey) ?? null;
70219
+ }
70220
+ function computeContextSignals(hostKey, systemPrompt, tools, messages) {
70221
+ const prefixHash = computePromptPrefixHash(systemPrompt, tools);
70222
+ const previous = signalMemories.get(hostKey);
70223
+ const prefixHashChanged = previous !== void 0 && previous.hash !== prefixHash;
70224
+ if (previous?.hash !== prefixHash) {
70225
+ signalMemories.set(hostKey, { hash: prefixHash });
70226
+ }
70227
+ const signals = {
70228
+ prefixHash,
70229
+ prefixHashChanged,
70230
+ cacheHitRate: calculateCumulativeCacheHitRate(messages)
70231
+ };
70232
+ lastSignals.set(hostKey, signals);
70233
+ return signals;
70234
+ }
70235
+
70174
70236
  // src/commands/interactive-session-commands.ts
70175
70237
  import * as os6 from "os";
70176
70238
 
@@ -70359,6 +70421,387 @@ var SessionSelectorComponent = class extends Container {
70359
70421
  }
70360
70422
  };
70361
70423
 
70424
+ // src/core/context-report.ts
70425
+ var APPROX_BYTES_PER_TOKEN = 4;
70426
+ function estimateStringTokens(text) {
70427
+ if (!text) return 0;
70428
+ return Math.ceil(Buffer.byteLength(text, "utf8") / APPROX_BYTES_PER_TOKEN);
70429
+ }
70430
+ function formatReportTokens(count) {
70431
+ if (count < 1e3) return String(count);
70432
+ if (count < 1e4) return `${(count / 1e3).toFixed(1)}k`;
70433
+ if (count < 1e6) return `${Math.round(count / 1e3)}k`;
70434
+ if (count < 1e7) return `${(count / 1e6).toFixed(1)}M`;
70435
+ return `${Math.round(count / 1e6)}M`;
70436
+ }
70437
+ function buildSystemPromptBlocks(systemPrompt, skillsSection, knowledgeSection) {
70438
+ const totalTokens = estimateStringTokens(systemPrompt);
70439
+ const skillsTokens = skillsSection && systemPrompt.includes(skillsSection.trim()) ? estimateStringTokens(skillsSection) : null;
70440
+ const knowledgeTokens = knowledgeSection && systemPrompt.includes(knowledgeSection.trim()) ? estimateStringTokens(knowledgeSection) : null;
70441
+ const accounted = (skillsTokens ?? 0) + (knowledgeTokens ?? 0);
70442
+ return {
70443
+ totalTokens,
70444
+ baseTokens: Math.max(0, totalTokens - accounted),
70445
+ skillsTokens,
70446
+ knowledgeTokens
70447
+ };
70448
+ }
70449
+ function escapeXmlName(name) {
70450
+ return name.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
70451
+ }
70452
+ function extractSkillBlocks(section) {
70453
+ if (!section) return [];
70454
+ return section.split(/(?=<skill>)/).map((block) => block.trim()).filter((block) => block.startsWith("<skill>"));
70455
+ }
70456
+ function buildSkillRows(skillsSection, skills) {
70457
+ const blocks = extractSkillBlocks(skillsSection);
70458
+ if (blocks.length === 0) {
70459
+ return [
70460
+ {
70461
+ id: "skills:none",
70462
+ label: "no skills in prompt",
70463
+ estTokens: 0
70464
+ }
70465
+ ];
70466
+ }
70467
+ return blocks.map((block, index) => {
70468
+ const escapedNameMatch = block.match(/<name>([\s\S]*?)<\/name>/);
70469
+ const escapedName = escapedNameMatch?.[1] ?? "";
70470
+ const skill = skills.find((candidate) => escapeXmlName(candidate.name) === escapedName) ?? skills.find((candidate) => block.includes(escapeXmlName(candidate.name)));
70471
+ const estTokens = estimateStringTokens(block);
70472
+ const source = skill?.source ? `source:${skill.source}` : void 0;
70473
+ const detail = skill?.inlineContent !== void 0 ? "inline content (already loaded)" : skill?.filePath ?? void 0;
70474
+ return {
70475
+ id: `skill:${skill?.name ?? index}`,
70476
+ label: skill?.name ?? `<skill #${index + 1}>`,
70477
+ estTokens,
70478
+ note: source,
70479
+ detail,
70480
+ drill: {
70481
+ kind: "text",
70482
+ title: `Skill \xB7 ${skill?.name ?? `#${index + 1}`}`,
70483
+ text: block
70484
+ }
70485
+ };
70486
+ }).sort((a, b2) => b2.estTokens - a.estTokens);
70487
+ }
70488
+ function parseProjectContextSection(section) {
70489
+ const parts = section.split(/^### (.+)$/m);
70490
+ const files = [];
70491
+ for (let i = 1; i + 1 < parts.length + 1; i += 2) {
70492
+ const path34 = (parts[i] ?? "").trim();
70493
+ const content = (parts[i + 1] ?? "").trim();
70494
+ if (!path34 || !content) continue;
70495
+ files.push({ path: path34, content });
70496
+ }
70497
+ return files;
70498
+ }
70499
+ function buildKnowledgeRows(files) {
70500
+ if (files.length === 0) {
70501
+ return [
70502
+ {
70503
+ id: "knowledge:none",
70504
+ label: "no AGENTS.md files found",
70505
+ estTokens: 0
70506
+ }
70507
+ ];
70508
+ }
70509
+ return files.map((file) => {
70510
+ const lineCount = file.content.split("\n").length;
70511
+ return {
70512
+ id: `knowledge:${file.path}`,
70513
+ label: file.path,
70514
+ estTokens: estimateStringTokens(file.content),
70515
+ note: `${lineCount} lines`,
70516
+ detail: `AGENTS.md`,
70517
+ drill: {
70518
+ kind: "text",
70519
+ title: `Project Context \xB7 ${file.path}`,
70520
+ text: file.content
70521
+ }
70522
+ };
70523
+ });
70524
+ }
70525
+ function estimateToolTokens(tool) {
70526
+ const parameters = tool.parameters === void 0 ? "" : JSON.stringify(tool.parameters);
70527
+ return estimateStringTokens(`${tool.name}
70528
+ ${tool.description ?? ""}
70529
+ ${parameters}`);
70530
+ }
70531
+ var MAX_TOOL_DRILL_CHARS = 6e3;
70532
+ function buildToolRows(tools) {
70533
+ if (tools.length === 0) {
70534
+ return [{ id: "tools:none", label: "no tools registered", estTokens: 0 }];
70535
+ }
70536
+ return tools.map((tool) => {
70537
+ const paramsJson = tool.parameters === void 0 ? "" : JSON.stringify(tool.parameters, null, 2);
70538
+ const drillText = [
70539
+ tool.description ? `# description
70540
+ ${tool.description}` : "",
70541
+ paramsJson ? `# parameters (schema)
70542
+ ${paramsJson}` : ""
70543
+ ].filter(Boolean).join("\n\n").slice(0, MAX_TOOL_DRILL_CHARS);
70544
+ return {
70545
+ id: `tool:${tool.name}`,
70546
+ label: tool.name,
70547
+ estTokens: estimateToolTokens(tool),
70548
+ note: tool.label && tool.label !== tool.name ? tool.label : void 0,
70549
+ detail: tool.description?.split("\n")[0]?.slice(0, 120),
70550
+ drill: { kind: "text", title: `Tool \xB7 ${tool.name}`, text: drillText }
70551
+ };
70552
+ }).sort((a, b2) => b2.estTokens - a.estTokens);
70553
+ }
70554
+ function entryRoleTag(entry) {
70555
+ return entry.role ?? entry.type;
70556
+ }
70557
+ function buildMessageRows(entries, compactionTokensBefore) {
70558
+ if (entries.length === 0) {
70559
+ return [{ id: "messages:none", label: "active context is empty", estTokens: 0 }];
70560
+ }
70561
+ return entries.map((entry) => {
70562
+ const roleTag = entryRoleTag(entry);
70563
+ const isCompaction = entry.type === "compaction";
70564
+ const tokensBefore = isCompaction ? compactionTokensBefore?.(entry.id) : void 0;
70565
+ return {
70566
+ id: `entry:${entry.id}`,
70567
+ label: `#${entry.seq} ${roleTag}`,
70568
+ estTokens: entry.estTokens,
70569
+ roleTag,
70570
+ marker: isCompaction ? "\u2691" : void 0,
70571
+ note: isCompaction ? tokensBefore !== void 0 ? `checkpoint \xB7 context was ~${formatReportTokens(tokensBefore)} before` : "checkpoint" : entry.bodyBytes > 0 ? `${formatReportTokens(entry.bodyBytes)}B body` : void 0,
70572
+ detail: entry.preview,
70573
+ drill: { kind: "entry", entryId: entry.id }
70574
+ };
70575
+ });
70576
+ }
70577
+ var MAX_PROMPT_DRILL_CHARS = 12e3;
70578
+ function buildContextReportData(inputs) {
70579
+ const knowledgeSectionText = inputs.knowledgeFiles.map((file) => `### ${file.path}
70580
+ ${file.content}`).join("\n\n");
70581
+ const blocks = buildSystemPromptBlocks(
70582
+ inputs.systemPrompt,
70583
+ inputs.skillsSection,
70584
+ knowledgeSectionText
70585
+ );
70586
+ const systemPromptRows = [
70587
+ {
70588
+ id: "prompt:base",
70589
+ label: "base prompt (persona + tool guidance + guidelines + docs)",
70590
+ estTokens: blocks.baseTokens,
70591
+ drill: {
70592
+ kind: "text",
70593
+ title: "System Prompt",
70594
+ text: inputs.systemPrompt.slice(0, MAX_PROMPT_DRILL_CHARS)
70595
+ }
70596
+ }
70597
+ ];
70598
+ if (blocks.skillsTokens !== null || inputs.skills.length > 0) {
70599
+ systemPromptRows.push({
70600
+ id: "prompt:skills",
70601
+ label: "skills section",
70602
+ estTokens: blocks.skillsTokens ?? 0,
70603
+ note: `${inputs.skills.length} skills`,
70604
+ drill: {
70605
+ kind: "rows",
70606
+ title: "System Prompt \u203A Skills",
70607
+ rows: buildSkillRows(inputs.skillsSection, inputs.skills),
70608
+ rowKind: "plain"
70609
+ }
70610
+ });
70611
+ }
70612
+ if (blocks.knowledgeTokens !== null || inputs.knowledgeFiles.length > 0) {
70613
+ systemPromptRows.push({
70614
+ id: "prompt:knowledge",
70615
+ label: "project context (AGENTS.md)",
70616
+ estTokens: blocks.knowledgeTokens ?? 0,
70617
+ note: `${inputs.knowledgeFiles.length} files`,
70618
+ drill: {
70619
+ kind: "rows",
70620
+ title: "System Prompt \u203A Project Context",
70621
+ rows: buildKnowledgeRows(inputs.knowledgeFiles),
70622
+ rowKind: "plain"
70623
+ }
70624
+ });
70625
+ }
70626
+ const systemPromptSection = {
70627
+ id: "system-prompt",
70628
+ title: "System prompt",
70629
+ estTokens: blocks.totalTokens,
70630
+ note: `base ~${formatReportTokens(blocks.baseTokens)}`,
70631
+ rows: systemPromptRows
70632
+ };
70633
+ const toolRows = buildToolRows(inputs.tools);
70634
+ const toolsTokens = toolRows.reduce((sum, row) => sum + row.estTokens, 0);
70635
+ const toolsSection = {
70636
+ id: "tools",
70637
+ title: `Tools (${inputs.tools.length})`,
70638
+ estTokens: toolsTokens,
70639
+ note: toolRows[0] ? `largest: ${toolRows[0].label} ~${formatReportTokens(toolRows[0].estTokens)}` : void 0,
70640
+ rows: toolRows
70641
+ };
70642
+ const messageRows = buildMessageRows(inputs.entries, inputs.compactionTokensBefore);
70643
+ const messagesTokens = inputs.entries.reduce((sum, entry) => sum + entry.estTokens, 0);
70644
+ const messagesSection = {
70645
+ id: "messages",
70646
+ title: `Messages (${inputs.entries.length})`,
70647
+ estTokens: messagesTokens,
70648
+ rows: messageRows
70649
+ };
70650
+ const dynamicContextSection = {
70651
+ id: "dynamic-context",
70652
+ title: "Dynamic context",
70653
+ estTokens: 0,
70654
+ note: "unmetered",
70655
+ rows: [
70656
+ {
70657
+ id: "dynamic:runtime-mode",
70658
+ label: "runtime-mode per-turn injections are not counted in these estimates",
70659
+ estTokens: 0,
70660
+ detail: "the host injects a <runtime-mode-context> block (mode flags, date, active tool names) on every request via transformContext; it is part of the est\u2212actual gap below"
70661
+ }
70662
+ ]
70663
+ };
70664
+ const roleCountsMap = /* @__PURE__ */ new Map();
70665
+ for (const entry of inputs.entries) {
70666
+ const tag = entryRoleTag(entry);
70667
+ roleCountsMap.set(tag, (roleCountsMap.get(tag) ?? 0) + 1);
70668
+ }
70669
+ const roleCounts = [...roleCountsMap.entries()].map(([role, count]) => ({ role, count })).sort((a, b2) => b2.count - a.count || a.role.localeCompare(b2.role));
70670
+ const topEntries = [...inputs.entries].sort((a, b2) => b2.estTokens - a.estTokens).slice(0, 5).map((entry) => ({
70671
+ seq: entry.seq,
70672
+ label: `${entryRoleTag(entry)} \xB7 ${entry.preview}`.trim(),
70673
+ estTokens: entry.estTokens
70674
+ }));
70675
+ let latestCompaction = null;
70676
+ for (const entry of inputs.entries) {
70677
+ if (entry.type === "compaction") {
70678
+ latestCompaction = {
70679
+ seq: entry.seq,
70680
+ tokensBefore: inputs.compactionTokensBefore?.(entry.id)
70681
+ };
70682
+ }
70683
+ }
70684
+ const estimatedTotalTokens = blocks.totalTokens + toolsTokens + messagesTokens;
70685
+ return {
70686
+ sections: [systemPromptSection, toolsSection, messagesSection, dynamicContextSection],
70687
+ systemPromptTokens: blocks.totalTokens,
70688
+ toolsTokens,
70689
+ messagesTokens,
70690
+ dynamicContextTokens: 0,
70691
+ estimatedTotalTokens,
70692
+ actualTokens: inputs.actualTokens,
70693
+ contextWindow: inputs.contextWindow,
70694
+ autoCompactLimitTokens: inputs.autoCompactLimitTokens,
70695
+ reserveTokens: inputs.reserveTokens,
70696
+ promptPrefixHash: inputs.promptPrefixHash,
70697
+ promptPrefixChanged: inputs.promptPrefixChanged,
70698
+ roleCounts,
70699
+ topEntries,
70700
+ latestCompaction,
70701
+ entryCount: inputs.entries.length
70702
+ };
70703
+ }
70704
+ var identityTheme = {
70705
+ bold: (text) => text,
70706
+ fg: (_token, text) => text
70707
+ };
70708
+ function padEndVisible(text, width) {
70709
+ return text.length >= width ? text : text + " ".repeat(width - text.length);
70710
+ }
70711
+ function formatReportHeadline(data) {
70712
+ const est = `~${formatReportTokens(data.estimatedTotalTokens)}`;
70713
+ if (data.contextWindow > 0) {
70714
+ const percent = (data.estimatedTotalTokens / data.contextWindow * 100).toFixed(1);
70715
+ return `est ${est} / ${formatReportTokens(data.contextWindow)} (${percent}%)`;
70716
+ }
70717
+ return `est ${est} / window unknown`;
70718
+ }
70719
+ function buildContextOverviewText(data, themeInput) {
70720
+ const theme2 = themeInput ?? identityTheme;
70721
+ const lines = [];
70722
+ const sections = data.sections;
70723
+ lines.push(theme2.bold("Context Report") + theme2.fg("dim", ` \xB7 ${formatReportHeadline(data)}`));
70724
+ const labelWidth = 20;
70725
+ const branch = (index) => index === sections.length ? "\u2514" : "\u251C";
70726
+ sections.forEach((section, index) => {
70727
+ const label = padEndVisible(` ${branch(index)} ${section.title}`, labelWidth);
70728
+ const tokens = `~${formatReportTokens(section.estTokens)}`;
70729
+ const note = section.note ? theme2.fg("dim", ` ${section.note}`) : "";
70730
+ lines.push(`${label}${theme2.fg("accent", tokens)}${note}`);
70731
+ if (section.id === "messages") {
70732
+ const counts = data.roleCounts.map((rc) => `${rc.role} ${rc.count}`).join(" \xB7 ");
70733
+ if (counts) {
70734
+ lines.push(`${" ".repeat(labelWidth + 1)}${theme2.fg("dim", counts)}`);
70735
+ }
70736
+ for (const top of data.topEntries.slice(0, 3)) {
70737
+ const preview = top.label.length > 60 ? `${top.label.slice(0, 59)}\u2026` : top.label;
70738
+ lines.push(
70739
+ `${" ".repeat(labelWidth + 1)}${theme2.fg("dim", `top: #${top.seq} ${preview} (~${formatReportTokens(top.estTokens)})`)}`
70740
+ );
70741
+ }
70742
+ if (data.latestCompaction) {
70743
+ const before = data.latestCompaction.tokensBefore !== void 0 ? ` \xB7 context was ~${formatReportTokens(data.latestCompaction.tokensBefore)} before` : "";
70744
+ lines.push(
70745
+ `${" ".repeat(labelWidth + 1)}${theme2.fg("dim", `\u2691 compact checkpoint @#${data.latestCompaction.seq}${before}`)}`
70746
+ );
70747
+ }
70748
+ }
70749
+ });
70750
+ const autoCompact = data.autoCompactLimitTokens ? `triggers > ~${formatReportTokens(data.autoCompactLimitTokens)} (reserve ${formatReportTokens(data.reserveTokens)})` : "disabled";
70751
+ lines.push(
70752
+ `${padEndVisible(` ${branch(sections.length)} auto-compact`, labelWidth)}${theme2.fg("dim", autoCompact)}`
70753
+ );
70754
+ const reconciliation = data.actualTokens !== null ? `actual ${formatReportTokens(data.actualTokens)} (last response) \xB7 est\u2212actual \u2248 ${formatReportTokens(
70755
+ Math.abs(data.estimatedTotalTokens - data.actualTokens)
70756
+ )}${data.estimatedTotalTokens >= data.actualTokens ? " overhead" : " undercount"}` : "actual unknown until the next model response";
70757
+ lines.push(` ${theme2.fg("dim", reconciliation)}`);
70758
+ lines.push(theme2.fg("dim", " /debugs/context \xB7 Enter to drill into a section, Esc to go back"));
70759
+ return lines.join("\n");
70760
+ }
70761
+ function buildWindowRows(data) {
70762
+ const rows = [
70763
+ {
70764
+ id: "window:window",
70765
+ label: "context window",
70766
+ estTokens: data.contextWindow,
70767
+ valueText: data.contextWindow > 0 ? formatReportTokens(data.contextWindow) : "?",
70768
+ note: data.contextWindow > 0 ? `${(data.estimatedTotalTokens / data.contextWindow * 100).toFixed(1)}% used (est)` : "unknown"
70769
+ },
70770
+ {
70771
+ id: "window:estimated",
70772
+ label: "estimated total (prompt + tools + messages)",
70773
+ estTokens: data.estimatedTotalTokens,
70774
+ note: `prompt ~${formatReportTokens(data.systemPromptTokens)} \xB7 tools ~${formatReportTokens(data.toolsTokens)} \xB7 messages ~${formatReportTokens(data.messagesTokens)}`
70775
+ }
70776
+ ];
70777
+ if (data.actualTokens !== null) {
70778
+ rows.push({
70779
+ id: "window:actual",
70780
+ label: "actual (last model response)",
70781
+ estTokens: data.actualTokens,
70782
+ valueText: formatReportTokens(data.actualTokens),
70783
+ note: `gap \u2248 ${formatReportTokens(Math.abs(data.estimatedTotalTokens - data.actualTokens))}`
70784
+ });
70785
+ }
70786
+ rows.push({
70787
+ id: "window:auto-compact",
70788
+ label: "auto-compact threshold",
70789
+ estTokens: data.autoCompactLimitTokens ?? 0,
70790
+ valueText: data.autoCompactLimitTokens ? `>${formatReportTokens(data.autoCompactLimitTokens)}` : "off",
70791
+ note: data.autoCompactLimitTokens ? `reserve ${formatReportTokens(data.reserveTokens)}` : "disabled"
70792
+ });
70793
+ if (data.promptPrefixHash) {
70794
+ rows.push({
70795
+ id: "window:prefix-hash",
70796
+ label: "prompt+tools hash (cache prefix)",
70797
+ estTokens: 0,
70798
+ valueText: data.promptPrefixHash,
70799
+ note: data.promptPrefixChanged ? "changed since last turn \u2014 provider cache invalidated \u26A1" : "stable \u2014 cache-friendly"
70800
+ });
70801
+ }
70802
+ return rows;
70803
+ }
70804
+
70362
70805
  // src/components/tree-selector.ts
70363
70806
  var RESET = "\x1B[0m";
70364
70807
  var INVERSE = "\x1B[7m";
@@ -70829,12 +71272,13 @@ var TreeSelectorComponent = class {
70829
71272
  const activeIndicator = this.options.activePathSeqs.has(row.summary.seq) ? theme.fg("success", TREE_CHARS.active) : DIM + TREE_CHARS.inactive + RESET;
70830
71273
  const expandIndicator = row.inWindowChildren ? theme.fg("dim", this.collapsed.has(row.summary.id) ? "\u25B6 " : "\u25BC ") : row.hasChildren ? theme.fg("dim", "\xB7\xB7") : " ";
70831
71274
  const role = roleColor(roleName(row.summary));
71275
+ const tokenNote = row.summary.estTokens > 0 ? theme.fg("dim", ` ~${formatReportTokens(row.summary.estTokens)}`) : "";
70832
71276
  const indicatorWidth = visibleWidth(activeIndicator) + visibleWidth(expandIndicator) + visibleWidth(role) + 2;
70833
- const prefixBudget = Math.max(0, Math.min(Math.floor(width * 0.35), width - indicatorWidth - MIN_BODY_WIDTH));
71277
+ const prefixBudget = Math.max(0, Math.min(Math.floor(width * 0.35), width - indicatorWidth - MIN_BODY_WIDTH - visibleWidth(tokenNote)));
70834
71278
  const displayPrefix = clampPrefixForDisplay(row.prefix, prefixBudget);
70835
- const availableWidth = Math.max(0, width - visibleWidth(displayPrefix) - indicatorWidth);
71279
+ const availableWidth = Math.max(0, width - visibleWidth(displayPrefix) - indicatorWidth - visibleWidth(tokenNote) - 2);
70836
71280
  const content = truncateToWidth(row.summary.preview ?? "", availableWidth);
70837
- lines.push(createLine(`${displayPrefix}${activeIndicator}${expandIndicator}${role}: ${content}`, width, i === this.selectedIndex));
71281
+ lines.push(createLine(`${displayPrefix}${activeIndicator}${expandIndicator}${role}: ${content}${tokenNote}`, width, i === this.selectedIndex));
70838
71282
  }
70839
71283
  for (let i = endIndex - startIndex; i < this.maxVisibleLines; i++) {
70840
71284
  lines.push(createLine("", width, false));
@@ -72173,6 +72617,29 @@ function deriveSessionTitle(options) {
72173
72617
  }
72174
72618
  return `S:${activeSession.id.slice(-6)}`;
72175
72619
  }
72620
+ function calculateSessionUsageTotals(messages) {
72621
+ let input = 0;
72622
+ let output = 0;
72623
+ let cacheRead = 0;
72624
+ let cacheWrite = 0;
72625
+ let cost = 0;
72626
+ for (const message of messages) {
72627
+ if (message.role !== "assistant") continue;
72628
+ const usage = message.usage;
72629
+ if (!usage) continue;
72630
+ input += usage.input;
72631
+ output += usage.output;
72632
+ cacheRead += usage.cacheRead;
72633
+ cacheWrite += usage.cacheWrite;
72634
+ cost += usage.cost?.total ?? 0;
72635
+ }
72636
+ return { input, output, cacheRead, cacheWrite, cost };
72637
+ }
72638
+ function formatTokensCompact(count) {
72639
+ if (count < 1e3) return String(count);
72640
+ if (count < 1e6) return `${(count / 1e3).toFixed(1)}k`;
72641
+ return `${(count / 1e6).toFixed(2)}M`;
72642
+ }
72176
72643
  function buildSessionInfoText(options) {
72177
72644
  const displayCwd = options.homeDir && options.cwd.startsWith(options.homeDir) ? `~${options.cwd.slice(options.homeDir.length)}` : options.cwd;
72178
72645
  let info = options.theme.bold("Session Info") + "\n\n";
@@ -72195,6 +72662,39 @@ function buildSessionInfoText(options) {
72195
72662
  `;
72196
72663
  info += `${options.theme.fg("dim", "Thinking Level:")} ${options.thinkingLevel}
72197
72664
  `;
72665
+ if (options.messages && options.messages.length > 0) {
72666
+ const totals = calculateSessionUsageTotals(options.messages);
72667
+ const turnCount = options.messages.filter((m2) => m2.role === "assistant").length;
72668
+ if (turnCount > 0) {
72669
+ info += `
72670
+ ${options.theme.bold("Usage (active context)")}
72671
+ `;
72672
+ info += `${options.theme.fg("dim", "Turns:")} ${turnCount}
72673
+ `;
72674
+ info += `${options.theme.fg("dim", "Input:")} ${totals.input.toLocaleString()}
72675
+ `;
72676
+ info += `${options.theme.fg("dim", "Output:")} ${totals.output.toLocaleString()}
72677
+ `;
72678
+ if (totals.cacheRead > 0) {
72679
+ info += `${options.theme.fg("dim", "Cache Read:")} ${totals.cacheRead.toLocaleString()}
72680
+ `;
72681
+ }
72682
+ if (totals.cacheWrite > 0) {
72683
+ info += `${options.theme.fg("dim", "Cache Write:")} ${totals.cacheWrite.toLocaleString()}
72684
+ `;
72685
+ }
72686
+ if (totals.cost > 0) {
72687
+ info += `${options.theme.fg("dim", "Cost:")} $${totals.cost.toFixed(4)}
72688
+ `;
72689
+ }
72690
+ const hitRateBase = totals.input + totals.cacheRead;
72691
+ if (hitRateBase > 0 && totals.cacheRead > 0) {
72692
+ const hitRate = (totals.cacheRead / hitRateBase * 100).toFixed(1);
72693
+ info += `${options.theme.fg("dim", "Cache Hit Rate:")} ${hitRate}% (~${formatTokensCompact(hitRateBase)} served)
72694
+ `;
72695
+ }
72696
+ }
72697
+ }
72198
72698
  return info;
72199
72699
  }
72200
72700
  function extractUserText(msg) {
@@ -72326,6 +72826,7 @@ async function loadSession(host, sessionId) {
72326
72826
  contextPercent,
72327
72827
  contextWindow
72328
72828
  });
72829
+ applyContextSignalsToFooter(host);
72329
72830
  host.ui.requestRender();
72330
72831
  setTimeout(() => host.ui.requestRender(), 100);
72331
72832
  } catch (error) {
@@ -73103,8 +73604,9 @@ var BUILTIN_SLASH_COMMANDS = [
73103
73604
  { name: "settings/calm", description: "Toggle calm mode: show only questions and final answers" },
73104
73605
  { name: "settings/yolo", description: "Toggle or set YOLO mode" },
73105
73606
  { name: "debugs/cache-test", description: "Run provider cache diagnostics with a dedicated 2-request probe" },
73106
- { name: "debugs/context", description: "Append a context summary to ~/.pie/logs/pie-cli.log" },
73107
- { name: "debugs/package", description: "Append a package summary to ~/.pie/logs/pie-cli.log" },
73607
+ { name: "debugs/context", description: "Context composition report: sections, tokens, drill-down" },
73608
+ { name: "debugs/trace", description: "Session trace: timeline, tool stats, runtime events" },
73609
+ { name: "debugs/boot", description: "Boot composition pipeline: how the runtime was assembled" },
73108
73610
  { name: "settings/quit", description: "Exit Pie" },
73109
73611
  { name: "skills", description: "Manage skills: list, toggle enable/disable, invoke" },
73110
73612
  { name: "skills/list", description: "List available skills (plain text)" },
@@ -73362,6 +73864,7 @@ function bindInteractiveRuntimeEvents(host) {
73362
73864
  contextPercent,
73363
73865
  contextWindow
73364
73866
  });
73867
+ applyContextSignalsToFooter(host);
73365
73868
  host.safeRequestRender();
73366
73869
  host.sessionManager.save().catch((err) => {
73367
73870
  console.error("[Session] Auto-save failed:", err);
@@ -73810,7 +74313,7 @@ function assistantDisplayBlocks(content) {
73810
74313
  }
73811
74314
 
73812
74315
  // src/commands/interactive-debug-commands.ts
73813
- import { createHash as createHash4 } from "crypto";
74316
+ import { createHash as createHash5 } from "crypto";
73814
74317
  function errorMessage(error) {
73815
74318
  return error instanceof Error ? error.message : String(error);
73816
74319
  }
@@ -73848,7 +74351,7 @@ async function handleCacheTest(host) {
73848
74351
  timestamp: fixedTimestamp
73849
74352
  }
73850
74353
  ];
73851
- const prefixHash = createHash4("sha256").update(
74354
+ const prefixHash = createHash5("sha256").update(
73852
74355
  JSON.stringify({
73853
74356
  systemPrompt: stablePrefix,
73854
74357
  tools: toolDefs.map((tool) => ({
@@ -73957,126 +74460,6 @@ async function handleCacheTest(host) {
73957
74460
  host.chatContainer.addChild(new Spacer(1));
73958
74461
  host.ui.requestRender();
73959
74462
  }
73960
- async function handleDebugContextCommand(host) {
73961
- host.chatContainer.addChild(new Spacer(1));
73962
- host.chatContainer.addChild(new Text(theme.bold("Debug Context"), 1, 0));
73963
- host.ui.requestRender();
73964
- try {
73965
- const state = host.agent.state;
73966
- const debugInfo = {
73967
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
73968
- systemPrompt: state.systemPrompt,
73969
- model: state.model,
73970
- thinkingLevel: state.thinkingLevel,
73971
- tools: state.tools.map((t) => ({
73972
- name: t.name,
73973
- description: t.description,
73974
- label: t.label
73975
- })),
73976
- messageCount: state.messages.length,
73977
- messages: state.messages,
73978
- isStreaming: state.isStreaming,
73979
- pendingToolCalls: Array.from(state.pendingToolCalls)
73980
- };
73981
- getLogger().child({ module: "context_dump" }).info("Agent context snapshot", {
73982
- snapshot: {
73983
- timestamp: debugInfo.timestamp,
73984
- modelId: debugInfo.model?.id ?? "",
73985
- provider: debugInfo.model?.provider ?? "",
73986
- thinkingLevel: debugInfo.thinkingLevel,
73987
- toolCount: debugInfo.tools.length,
73988
- messageCount: debugInfo.messageCount,
73989
- isStreaming: debugInfo.isStreaming,
73990
- pendingToolCalls: debugInfo.pendingToolCalls.length
73991
- }
73992
- });
73993
- host.chatContainer.addChild(new Text(
73994
- theme.fg("success", "\u2713 Context summary appended to ~/.pie/logs/pie-cli.log"),
73995
- 1,
73996
- 0
73997
- ));
73998
- host.chatContainer.addChild(new Text(
73999
- theme.fg("dim", ` Messages: ${state.messages.length}, Tools: ${state.tools.length}`),
74000
- 1,
74001
- 0
74002
- ));
74003
- } catch (err) {
74004
- host.showError(`Failed to save context: ${errorMessage(err)}`);
74005
- }
74006
- host.chatContainer.addChild(new Spacer(1));
74007
- host.ui.requestRender();
74008
- }
74009
- async function handleDebugPackageCommand(host) {
74010
- host.chatContainer.addChild(new Spacer(1));
74011
- host.chatContainer.addChild(new Text(theme.bold("Debug Package"), 1, 0));
74012
- host.ui.requestRender();
74013
- try {
74014
- const messages = host.agent.state.messages;
74015
- const packageInfo = {
74016
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
74017
- totalMessages: messages.length,
74018
- conversation: messages.map((msg, index) => {
74019
- const baseInfo = {
74020
- index,
74021
- role: msg.role
74022
- };
74023
- if (msg.role === "user") {
74024
- return {
74025
- ...baseInfo,
74026
- direction: "\u2192 SEND",
74027
- content: typeof msg.content === "string" ? msg.content : JSON.stringify(msg.content, null, 2)
74028
- };
74029
- } else if (msg.role === "assistant") {
74030
- const assistantMsg = msg;
74031
- const toolCalls = assistantMsg.content.filter((c) => c.type === "toolCall").map((c) => ({ name: c.name, arguments: c.arguments, id: c.id }));
74032
- return {
74033
- ...baseInfo,
74034
- direction: "\u2190 RECEIVE",
74035
- content: assistantMsg.content,
74036
- toolCalls,
74037
- usage: assistantMsg.usage
74038
- };
74039
- } else if (msg.role === "toolResult") {
74040
- const toolMsg = msg;
74041
- return {
74042
- ...baseInfo,
74043
- direction: "\u2192 TOOL RESULT",
74044
- toolCallId: toolMsg.toolCallId,
74045
- content: toolMsg.content
74046
- };
74047
- }
74048
- return baseInfo;
74049
- })
74050
- };
74051
- getLogger().child({ module: "package_dump" }).info("Agent package snapshot", {
74052
- snapshot: {
74053
- timestamp: packageInfo.timestamp,
74054
- totalMessages: packageInfo.totalMessages,
74055
- userMessages: packageInfo.conversation.filter((m2) => m2.role === "user").length,
74056
- assistantMessages: packageInfo.conversation.filter((m2) => m2.role === "assistant").length,
74057
- toolMessages: packageInfo.conversation.filter((m2) => m2.role === "toolResult").length,
74058
- lastMessageRole: packageInfo.conversation.length > 0 ? packageInfo.conversation[packageInfo.conversation.length - 1].role : ""
74059
- }
74060
- });
74061
- host.chatContainer.addChild(new Text(
74062
- theme.fg("success", "\u2713 Package summary appended to ~/.pie/logs/pie-cli.log"),
74063
- 1,
74064
- 0
74065
- ));
74066
- const userCount = messages.filter((m2) => m2.role === "user").length;
74067
- const assistantCount = messages.filter((m2) => m2.role === "assistant").length;
74068
- const toolCount = messages.filter((m2) => m2.role === "toolResult").length;
74069
- host.chatContainer.addChild(new Text(
74070
- theme.fg("dim", ` User: ${userCount}, Assistant: ${assistantCount}, Tool: ${toolCount}`),
74071
- 1,
74072
- 0
74073
- ));
74074
- } catch (err) {
74075
- host.showError(`Failed to save package log: ${errorMessage(err)}`);
74076
- }
74077
- host.chatContainer.addChild(new Spacer(1));
74078
- host.ui.requestRender();
74079
- }
74080
74463
 
74081
74464
  // src/commands/interactive-skill-commands.ts
74082
74465
  import * as fs24 from "fs";
@@ -74653,6 +75036,944 @@ function handleToolsCommand(host, args) {
74653
75036
  showToolsManager(host);
74654
75037
  }
74655
75038
 
75039
+ // src/components/context-report-selector.ts
75040
+ var RESET2 = "\x1B[0m";
75041
+ var INVERSE2 = "\x1B[7m";
75042
+ var ROLE_FILTER_CYCLE = ["all", "user", "assistant", "toolResult", "compaction"];
75043
+ function createLine2(content, width, isSelected) {
75044
+ const normalized = content.replace(/[\r\n]+/g, " ");
75045
+ const body = isSelected ? INVERSE2 + normalized : normalized;
75046
+ return truncateToWidth(body + RESET2, width, "...", true);
75047
+ }
75048
+ function buildRootRows(sections, windowSection) {
75049
+ const rows = sections.map((section) => ({
75050
+ id: `section:${section.id}`,
75051
+ label: section.title,
75052
+ estTokens: section.estTokens,
75053
+ note: section.note,
75054
+ detail: section.id === "messages" ? "active context entries (Enter to list)" : void 0,
75055
+ drill: {
75056
+ kind: "rows",
75057
+ title: `${section.title}`,
75058
+ rows: section.rows,
75059
+ rowKind: section.id === "messages" ? "messages" : "plain"
75060
+ }
75061
+ }));
75062
+ if (windowSection) {
75063
+ rows.push({
75064
+ id: "section:window",
75065
+ label: windowSection.label,
75066
+ estTokens: windowSection.estTokens,
75067
+ valueText: windowSection.valueText,
75068
+ detail: windowSection.detail,
75069
+ drill: {
75070
+ kind: "rows",
75071
+ title: windowSection.title ?? windowSection.label,
75072
+ rows: windowSection.rows,
75073
+ rowKind: "plain"
75074
+ }
75075
+ });
75076
+ }
75077
+ return rows;
75078
+ }
75079
+ var ContextReportSelectorComponent = class {
75080
+ options;
75081
+ maxVisibleLines;
75082
+ views = [];
75083
+ selectedIndex = 0;
75084
+ searchQuery = "";
75085
+ statusMessage = null;
75086
+ _focused = false;
75087
+ get focused() {
75088
+ return this._focused;
75089
+ }
75090
+ set focused(value) {
75091
+ this._focused = value;
75092
+ }
75093
+ constructor(options) {
75094
+ this.options = options;
75095
+ this.maxVisibleLines = Math.max(15, Math.floor(options.terminalHeight * 0.6));
75096
+ this.views = [
75097
+ {
75098
+ kind: "list",
75099
+ title: options.title,
75100
+ rows: buildRootRows(options.sections, options.windowSection),
75101
+ rowKind: "root",
75102
+ roleFilter: "all",
75103
+ sortBySize: false
75104
+ }
75105
+ ];
75106
+ }
75107
+ // ---------------------------------------------------------------------------
75108
+ // Filtering / sorting
75109
+ // ---------------------------------------------------------------------------
75110
+ get currentView() {
75111
+ return this.views[this.views.length - 1];
75112
+ }
75113
+ visibleListRows(view) {
75114
+ let rows = view.rows;
75115
+ if (view.roleFilter !== "all") {
75116
+ rows = rows.filter((row) => row.roleTag === view.roleFilter);
75117
+ }
75118
+ if (view.rowKind === "messages" && view.sortBySize) {
75119
+ rows = [...rows].sort((a, b2) => b2.estTokens - a.estTokens);
75120
+ }
75121
+ if (this.searchQuery) {
75122
+ const query = this.searchQuery.toLowerCase();
75123
+ rows = rows.filter(
75124
+ (row) => `${row.label} ${row.detail ?? ""} ${row.note ?? ""} ${row.roleTag ?? ""}`.toLowerCase().includes(query)
75125
+ );
75126
+ }
75127
+ return rows;
75128
+ }
75129
+ applyFilterChange() {
75130
+ this.selectedIndex = 0;
75131
+ this.statusMessage = null;
75132
+ }
75133
+ // ---------------------------------------------------------------------------
75134
+ // Navigation
75135
+ // ---------------------------------------------------------------------------
75136
+ moveUp() {
75137
+ const view = this.currentView;
75138
+ if (view.kind === "list") {
75139
+ const rows = this.visibleListRows(view);
75140
+ if (rows.length > 0) {
75141
+ this.selectedIndex = (this.selectedIndex - 1 + rows.length) % rows.length;
75142
+ }
75143
+ } else {
75144
+ view.scroll = Math.max(0, view.scroll - 1);
75145
+ }
75146
+ }
75147
+ moveDown() {
75148
+ const view = this.currentView;
75149
+ if (view.kind === "list") {
75150
+ const rows = this.visibleListRows(view);
75151
+ if (rows.length > 0) {
75152
+ this.selectedIndex = (this.selectedIndex + 1) % rows.length;
75153
+ }
75154
+ } else {
75155
+ view.scroll = Math.min(Math.max(0, view.lines.length - 1), view.scroll + 1);
75156
+ }
75157
+ }
75158
+ pageUp() {
75159
+ const view = this.currentView;
75160
+ if (view.kind === "list") {
75161
+ this.selectedIndex = Math.max(0, this.selectedIndex - this.maxVisibleLines);
75162
+ } else {
75163
+ view.scroll = Math.max(0, view.scroll - this.maxVisibleLines);
75164
+ }
75165
+ }
75166
+ pageDown() {
75167
+ const view = this.currentView;
75168
+ if (view.kind === "list") {
75169
+ const rows = this.visibleListRows(view);
75170
+ this.selectedIndex = Math.min(Math.max(0, rows.length - 1), this.selectedIndex + this.maxVisibleLines);
75171
+ } else {
75172
+ view.scroll = Math.min(
75173
+ Math.max(0, view.lines.length - 1),
75174
+ view.scroll + this.maxVisibleLines
75175
+ );
75176
+ }
75177
+ }
75178
+ pushTextView(title, text) {
75179
+ this.searchQuery = "";
75180
+ this.views.push({
75181
+ kind: "text",
75182
+ title,
75183
+ lines: text.replace(/\r\n/g, "\n").split("\n"),
75184
+ scroll: 0
75185
+ });
75186
+ this.selectedIndex = 0;
75187
+ this.statusMessage = null;
75188
+ }
75189
+ drillIntoSelected() {
75190
+ const view = this.currentView;
75191
+ if (view.kind !== "list") return;
75192
+ const rows = this.visibleListRows(view);
75193
+ const row = rows[this.selectedIndex];
75194
+ if (!row) return;
75195
+ if (!row.drill) {
75196
+ this.statusMessage = "no further detail";
75197
+ return;
75198
+ }
75199
+ switch (row.drill.kind) {
75200
+ case "rows":
75201
+ this.searchQuery = "";
75202
+ this.views.push({
75203
+ kind: "list",
75204
+ title: row.drill.title,
75205
+ rows: row.drill.rows,
75206
+ rowKind: row.drill.rowKind,
75207
+ roleFilter: "all",
75208
+ sortBySize: false
75209
+ });
75210
+ this.selectedIndex = 0;
75211
+ this.statusMessage = null;
75212
+ break;
75213
+ case "text":
75214
+ this.pushTextView(row.drill.title, row.drill.text);
75215
+ break;
75216
+ case "entry": {
75217
+ const text = this.options.getEntryText?.(row.drill.entryId) ?? null;
75218
+ if (text === null) {
75219
+ this.statusMessage = "entry body unavailable";
75220
+ } else {
75221
+ this.pushTextView(`Entry \xB7 ${row.label}`, text);
75222
+ }
75223
+ break;
75224
+ }
75225
+ }
75226
+ }
75227
+ popView() {
75228
+ if (this.views.length > 1) {
75229
+ this.views.pop();
75230
+ this.selectedIndex = 0;
75231
+ this.statusMessage = null;
75232
+ } else {
75233
+ this.options.onDone();
75234
+ }
75235
+ }
75236
+ // ---------------------------------------------------------------------------
75237
+ // Input
75238
+ // ---------------------------------------------------------------------------
75239
+ handleInput(keyData) {
75240
+ const kb = getEditorKeybindings();
75241
+ if (kb.matches(keyData, "selectUp")) {
75242
+ this.moveUp();
75243
+ } else if (kb.matches(keyData, "selectDown")) {
75244
+ this.moveDown();
75245
+ } else if (kb.matches(keyData, "selectPageUp")) {
75246
+ this.pageUp();
75247
+ } else if (kb.matches(keyData, "selectPageDown")) {
75248
+ this.pageDown();
75249
+ } else if (kb.matches(keyData, "selectConfirm")) {
75250
+ this.drillIntoSelected();
75251
+ } else if (kb.matches(keyData, "selectCancel")) {
75252
+ if (this.searchQuery) {
75253
+ this.searchQuery = "";
75254
+ this.applyFilterChange();
75255
+ } else {
75256
+ this.popView();
75257
+ }
75258
+ } else if (matchesKey(keyData, "ctrl+u")) {
75259
+ const view = this.currentView;
75260
+ if (view.kind === "list" && view.rowKind === "messages") {
75261
+ const next = ROLE_FILTER_CYCLE[(ROLE_FILTER_CYCLE.indexOf(view.roleFilter) + 1) % ROLE_FILTER_CYCLE.length];
75262
+ view.roleFilter = next;
75263
+ this.applyFilterChange();
75264
+ }
75265
+ } else if ((keyData === "s" || keyData === "S") && this.searchQuery === "") {
75266
+ const view = this.currentView;
75267
+ if (view.kind === "list" && view.rowKind === "messages") {
75268
+ view.sortBySize = !view.sortBySize;
75269
+ this.applyFilterChange();
75270
+ }
75271
+ } else if (kb.matches(keyData, "deleteCharBackward")) {
75272
+ if (this.searchQuery.length > 0) {
75273
+ this.searchQuery = this.searchQuery.slice(0, -1);
75274
+ this.applyFilterChange();
75275
+ }
75276
+ } else {
75277
+ const hasControlChars = [...keyData].some((ch) => {
75278
+ const code = ch.charCodeAt(0);
75279
+ return code < 32 || code === 127 || code >= 128 && code <= 159;
75280
+ });
75281
+ if (!hasControlChars && keyData.length > 0 && this.searchQuery.length < 100) {
75282
+ this.searchQuery += keyData;
75283
+ this.applyFilterChange();
75284
+ }
75285
+ }
75286
+ }
75287
+ // ---------------------------------------------------------------------------
75288
+ // Rendering
75289
+ // ---------------------------------------------------------------------------
75290
+ invalidate() {
75291
+ }
75292
+ hintsLine(view) {
75293
+ if (view.kind === "text") {
75294
+ return " \u2191\u2193:scroll Esc:back";
75295
+ }
75296
+ const base = " \u2191\u2193:move Enter:drill Esc:back type:search PgUp/PgDn:page";
75297
+ if (view.rowKind === "messages") {
75298
+ return `${base} ctrl+u:role(${view.roleFilter}) s:sort(${view.sortBySize ? "size" : "seq"})`;
75299
+ }
75300
+ return base;
75301
+ }
75302
+ renderRowLine(row, width, selected) {
75303
+ const valueText = row.valueText ?? `~${formatReportTokens(row.estTokens)}`;
75304
+ const rightParts = [theme.fg("accent", valueText)];
75305
+ if (row.note) {
75306
+ rightParts.push(theme.fg("dim", row.note));
75307
+ }
75308
+ const right = rightParts.join(" ");
75309
+ const rightWidth = visibleWidth(right);
75310
+ const marker = row.marker ? theme.fg("warning", `${row.marker} `) : "";
75311
+ const detail = row.detail ? theme.fg("dim", ` \xB7 ${row.detail}`) : "";
75312
+ let left = ` ${marker}${row.label}${detail}`;
75313
+ const leftBudget = Math.max(12, width - rightWidth - 3);
75314
+ if (visibleWidth(left) > leftBudget) {
75315
+ left = truncateToWidth(left, leftBudget, "\u2026");
75316
+ }
75317
+ const gap = Math.max(1, width - visibleWidth(left) - rightWidth - 1);
75318
+ return createLine2(`${left}${" ".repeat(gap)}${right}`, width, selected);
75319
+ }
75320
+ render(width) {
75321
+ const lines = [];
75322
+ const totalLines = this.maxVisibleLines + 5;
75323
+ const view = this.currentView;
75324
+ const depthSuffix = this.views.length > 1 ? theme.fg("dim", ` (${this.views.length} levels)`) : "";
75325
+ lines.push(createLine2(` ${theme.bold(view.title)}${depthSuffix}`, width, false));
75326
+ lines.push(createLine2(this.hintsLine(view), width, false));
75327
+ const searchPart = this.searchQuery ? `Search: ${this.searchQuery}` : "";
75328
+ const statusPart = this.statusMessage ? theme.fg("warning", this.statusMessage) : "";
75329
+ const statusLine = [searchPart, statusPart].filter(Boolean).join(" \xB7 ");
75330
+ lines.push(createLine2(statusLine, width, false));
75331
+ lines.push(createLine2("", width, false));
75332
+ if (view.kind === "list") {
75333
+ const rows = this.visibleListRows(view);
75334
+ if (rows.length === 0) {
75335
+ const notice = this.searchQuery || view.roleFilter !== "all" ? " (no matching rows)" : " (empty)";
75336
+ lines.push(createLine2(notice, width, false));
75337
+ for (let i = 1; i < this.maxVisibleLines; i++) {
75338
+ lines.push(createLine2("", width, false));
75339
+ }
75340
+ } else {
75341
+ this.selectedIndex = Math.max(0, Math.min(this.selectedIndex, rows.length - 1));
75342
+ let startIndex = Math.max(
75343
+ 0,
75344
+ Math.min(
75345
+ this.selectedIndex - Math.floor(this.maxVisibleLines / 2),
75346
+ rows.length - this.maxVisibleLines
75347
+ )
75348
+ );
75349
+ const endIndex = Math.min(startIndex + this.maxVisibleLines, rows.length);
75350
+ if (endIndex - startIndex < this.maxVisibleLines && startIndex > 0) {
75351
+ startIndex = Math.max(0, endIndex - this.maxVisibleLines);
75352
+ }
75353
+ for (let i = startIndex; i < endIndex; i++) {
75354
+ lines.push(this.renderRowLine(rows[i], width, i === this.selectedIndex));
75355
+ }
75356
+ for (let i = endIndex - startIndex; i < this.maxVisibleLines; i++) {
75357
+ lines.push(createLine2("", width, false));
75358
+ }
75359
+ }
75360
+ const filterSuffix = view.roleFilter !== "all" ? ` [${view.roleFilter}]` : "";
75361
+ const sortSuffix = view.rowKind === "messages" && view.sortBySize ? " [size]" : "";
75362
+ lines.push(
75363
+ createLine2(
75364
+ ` (${this.selectedIndex + 1}/${this.visibleListRows(view).length})${filterSuffix}${sortSuffix}`,
75365
+ width,
75366
+ false
75367
+ )
75368
+ );
75369
+ } else {
75370
+ const total = view.lines.length;
75371
+ const start = Math.min(view.scroll, Math.max(0, total - 1));
75372
+ const end = Math.min(start + this.maxVisibleLines, total);
75373
+ for (let i = start; i < end; i++) {
75374
+ lines.push(createLine2(` ${view.lines[i]}`, width, false));
75375
+ }
75376
+ for (let i = end - start; i < this.maxVisibleLines; i++) {
75377
+ lines.push(createLine2("", width, false));
75378
+ }
75379
+ lines.push(
75380
+ createLine2(` (lines ${total > 0 ? start + 1 : 0}-${end} of ${total})`, width, false)
75381
+ );
75382
+ }
75383
+ while (lines.length < totalLines) {
75384
+ lines.push(createLine2("", width, false));
75385
+ }
75386
+ return lines.slice(0, totalLines);
75387
+ }
75388
+ };
75389
+
75390
+ // src/commands/interactive-context-command.ts
75391
+ var MAX_ENTRY_BODY_CHARS = 1e4;
75392
+ var MAX_ARCHIVED_ROWS = 200;
75393
+ function fetchArchivedSummaries(sessionManager, firstKeptSeq) {
75394
+ const result = [];
75395
+ let afterSeq;
75396
+ while (result.length < MAX_ARCHIVED_ROWS) {
75397
+ const page = sessionManager.getEntrySummariesPage({ limit: 100, afterSeq });
75398
+ if (page.summaries.length === 0) break;
75399
+ let crossedCutPoint = false;
75400
+ for (const summary of page.summaries) {
75401
+ if (summary.seq >= firstKeptSeq) {
75402
+ crossedCutPoint = true;
75403
+ break;
75404
+ }
75405
+ if (result.length >= MAX_ARCHIVED_ROWS) break;
75406
+ result.push(summary);
75407
+ }
75408
+ if (crossedCutPoint || !page.hasMore) break;
75409
+ afterSeq = page.summaries[page.summaries.length - 1].seq;
75410
+ }
75411
+ return result;
75412
+ }
75413
+ function enhanceCheckpointRowWithArchive(report, host, entries) {
75414
+ const messagesSection = report.sections.find((section) => section.id === "messages");
75415
+ const checkpoint = [...entries].reverse().find((entry) => entry.type === "compaction");
75416
+ if (!messagesSection || !checkpoint) return;
75417
+ const compactionEntry = host.sessionManager.getEntry(checkpoint.id);
75418
+ const firstKeptEntryId = compactionEntry?.compactionData?.firstKeptEntryId;
75419
+ if (!firstKeptEntryId) return;
75420
+ const firstKeptSeq = host.sessionManager.getEntrySummary(firstKeptEntryId)?.seq;
75421
+ if (firstKeptSeq === void 0) return;
75422
+ const archived = fetchArchivedSummaries(host.sessionManager, firstKeptSeq);
75423
+ if (archived.length === 0) return;
75424
+ const row = messagesSection.rows.find((candidate) => candidate.id === `entry:${checkpoint.id}`);
75425
+ if (!row) return;
75426
+ const truncated = archived.length >= MAX_ARCHIVED_ROWS;
75427
+ row.drill = {
75428
+ kind: "rows",
75429
+ title: `Archived @ checkpoint #${checkpoint.seq}`,
75430
+ rowKind: "plain",
75431
+ rows: [
75432
+ {
75433
+ id: "checkpoint-summary",
75434
+ label: "checkpoint summary",
75435
+ estTokens: checkpoint.estTokens,
75436
+ drill: {
75437
+ kind: "text",
75438
+ title: "Checkpoint Summary",
75439
+ text: compactionEntry?.compactionData?.summary ?? ""
75440
+ }
75441
+ },
75442
+ ...archived.map((summary) => ({
75443
+ id: `archived:${summary.id}`,
75444
+ label: `#${summary.seq} ${summary.role ?? summary.type}`,
75445
+ estTokens: summary.estTokens,
75446
+ detail: summary.preview,
75447
+ drill: { kind: "entry", entryId: summary.id }
75448
+ })),
75449
+ ...truncated ? [{
75450
+ id: "archived:truncated",
75451
+ label: `\u2026 archived list capped at ${MAX_ARCHIVED_ROWS} entries (oldest first)`,
75452
+ estTokens: 0
75453
+ }] : []
75454
+ ]
75455
+ };
75456
+ }
75457
+ function formatEntryBodyText(entry) {
75458
+ if (!entry) return null;
75459
+ if (entry.type === "compaction") {
75460
+ const summary = entry.compactionData?.summary ?? "";
75461
+ const before = entry.compactionData?.tokensBefore;
75462
+ const header = before !== void 0 ? `# compaction checkpoint (context was ~${formatReportTokens(before)} before)
75463
+ ` : "# compaction checkpoint\n";
75464
+ return `${header}
75465
+ ${summary}`.slice(0, MAX_ENTRY_BODY_CHARS) || null;
75466
+ }
75467
+ const message = entry.message;
75468
+ if (!message) return null;
75469
+ if (message.role === "user") {
75470
+ const parts = ["# user message"];
75471
+ const content = message.content;
75472
+ if (typeof content === "string") {
75473
+ parts.push(content);
75474
+ } else if (Array.isArray(content)) {
75475
+ for (const block of content) {
75476
+ if (block && block.type === "text" && typeof block.text === "string") {
75477
+ parts.push(block.text);
75478
+ } else if (block && "type" in block) {
75479
+ parts.push(`[${block.type}]`);
75480
+ }
75481
+ }
75482
+ }
75483
+ return parts.join("\n").slice(0, MAX_ENTRY_BODY_CHARS) || null;
75484
+ }
75485
+ if (message.role === "assistant") {
75486
+ const parts = ["# assistant message"];
75487
+ const usage = message.usage;
75488
+ if (usage) {
75489
+ parts.push(
75490
+ `usage: input ${usage.input} \xB7 output ${usage.output} \xB7 cacheRead ${usage.cacheRead} \xB7 cacheWrite ${usage.cacheWrite}`
75491
+ );
75492
+ }
75493
+ if (Array.isArray(message.content)) {
75494
+ for (const block of message.content) {
75495
+ if (block.type === "text") {
75496
+ parts.push(block.text);
75497
+ } else if (block.type === "thinking") {
75498
+ parts.push("[thinking omitted from context on most providers]");
75499
+ } else if (block.type === "toolCall") {
75500
+ parts.push(`\u2192 ${block.name}(${JSON.stringify(block.arguments)})`);
75501
+ }
75502
+ }
75503
+ }
75504
+ return parts.join("\n").slice(0, MAX_ENTRY_BODY_CHARS) || null;
75505
+ }
75506
+ if (message.role === "toolResult") {
75507
+ const parts = [`# tool result (${message.toolCallId})`];
75508
+ if (Array.isArray(message.content)) {
75509
+ for (const block of message.content) {
75510
+ if (block.type === "text") {
75511
+ parts.push(block.text);
75512
+ } else if (block.type === "image") {
75513
+ parts.push("[image]");
75514
+ } else if (block.type === "audio") {
75515
+ parts.push("[audio]");
75516
+ } else if (block.type === "video") {
75517
+ parts.push("[video]");
75518
+ } else {
75519
+ parts.push(`[${block.type}]`);
75520
+ }
75521
+ }
75522
+ }
75523
+ return parts.join("\n").slice(0, MAX_ENTRY_BODY_CHARS) || null;
75524
+ }
75525
+ return JSON.stringify(message, null, 2).slice(0, MAX_ENTRY_BODY_CHARS) || null;
75526
+ }
75527
+ function handleContextReportCommand(host) {
75528
+ const systemPrompt = host.agent.state.systemPrompt ?? "";
75529
+ const knowledgeFiles = parseProjectContextSection(host.knowledgeSection);
75530
+ const entries = host.sessionManager.getActiveContextSummaries();
75531
+ const usage = host.calculateContextUsage();
75532
+ const contextWindow = host.options.model?.contextWindow ?? 0;
75533
+ const reserveTokens = host.options.settingsManager.getAutoCompactReserveTokens();
75534
+ const autoCompactLimit = contextWindow > 0 ? getAutoCompactTokenLimit(contextWindow, reserveTokens) ?? null : null;
75535
+ const prefixHash = computePromptPrefixHash(
75536
+ systemPrompt,
75537
+ host.agent.state.tools.map((tool) => ({
75538
+ name: tool.name,
75539
+ description: tool.description,
75540
+ parameters: tool.parameters
75541
+ }))
75542
+ );
75543
+ const lastSignals2 = getLastContextSignals(host);
75544
+ const promptPrefixChanged = lastSignals2 !== null && lastSignals2.prefixHash !== prefixHash;
75545
+ const report = buildContextReportData({
75546
+ systemPrompt,
75547
+ skillsSection: host.skillsSection,
75548
+ knowledgeFiles,
75549
+ skills: host.skills.map((skill) => ({
75550
+ name: skill.name,
75551
+ description: skill.description,
75552
+ filePath: skill.filePath,
75553
+ baseDir: skill.baseDir,
75554
+ source: skill.source
75555
+ })),
75556
+ tools: host.agent.state.tools.map((tool) => ({
75557
+ name: tool.name,
75558
+ label: tool.label,
75559
+ description: tool.description,
75560
+ parameters: tool.parameters
75561
+ })),
75562
+ entries,
75563
+ compactionTokensBefore: (entryId) => host.sessionManager.getEntry(entryId)?.compactionData?.tokensBefore,
75564
+ contextWindow,
75565
+ actualTokens: usage.tokens,
75566
+ autoCompactLimitTokens: autoCompactLimit,
75567
+ reserveTokens,
75568
+ promptPrefixHash: prefixHash,
75569
+ promptPrefixChanged
75570
+ });
75571
+ enhanceCheckpointRowWithArchive(report, host, entries);
75572
+ host.chatContainer.addChild(new Spacer(1));
75573
+ host.chatContainer.addChild(new Text(buildContextOverviewText(report, theme), 1, 0));
75574
+ host.ui.requestRender();
75575
+ host.showSelector((done) => {
75576
+ const selector = new ContextReportSelectorComponent({
75577
+ title: "Context Report",
75578
+ sections: report.sections,
75579
+ windowSection: {
75580
+ label: "window & thresholds",
75581
+ estTokens: report.contextWindow,
75582
+ valueText: report.contextWindow > 0 ? formatReportTokens(report.contextWindow) : "?",
75583
+ title: "Window & Thresholds",
75584
+ detail: "context window, estimate vs actual, auto-compact",
75585
+ rows: buildWindowRows(report)
75586
+ },
75587
+ terminalHeight: host.ui.terminal.rows,
75588
+ getEntryText: (entryId) => formatEntryBodyText(host.sessionManager.getEntry(entryId)),
75589
+ onDone: done
75590
+ });
75591
+ return { component: selector, focus: selector };
75592
+ });
75593
+ }
75594
+
75595
+ // src/core/trace-report.ts
75596
+ var MAX_DRILL_CHARS = 8e3;
75597
+ function formatTime(at) {
75598
+ return new Date(at).toLocaleTimeString([], { hour12: false });
75599
+ }
75600
+ function formatDurationMs(ms) {
75601
+ if (ms < 1e3) return `${Math.round(ms)}ms`;
75602
+ if (ms < 6e4) return `${(ms / 1e3).toFixed(1)}s`;
75603
+ return `${Math.floor(ms / 6e4)}m${Math.round(ms % 6e4 / 1e3)}s`;
75604
+ }
75605
+ function detailsToText(event) {
75606
+ const header = [
75607
+ `# ${event.type}`,
75608
+ `at: ${new Date(event.at).toISOString()}`,
75609
+ event.summary ? `summary: ${event.summary}` : "",
75610
+ event.failure ? "failure: true" : ""
75611
+ ].filter(Boolean).join("\n");
75612
+ const details = event.details === void 0 ? "" : `
75613
+
75614
+ # details
75615
+ ${safeStringify(event.details).slice(0, MAX_DRILL_CHARS)}`;
75616
+ return `${header}${details}`;
75617
+ }
75618
+ function safeStringify(value) {
75619
+ try {
75620
+ return typeof value === "string" ? value : JSON.stringify(value, null, 2);
75621
+ } catch {
75622
+ return String(value);
75623
+ }
75624
+ }
75625
+ function buildTimelineRows(timeline) {
75626
+ if (timeline.length === 0) {
75627
+ return [{ id: "timeline:none", label: "no runtime events recorded yet", estTokens: 0 }];
75628
+ }
75629
+ return [...timeline].sort((a, b2) => b2.at - a.at).map((event, index) => ({
75630
+ id: `timeline:${event.at}:${index}`,
75631
+ label: `${formatTime(event.at)} ${event.type}`,
75632
+ estTokens: 0,
75633
+ detail: event.summary || void 0,
75634
+ marker: event.failure ? "\u2716" : void 0,
75635
+ drill: {
75636
+ kind: "text",
75637
+ title: `Event \xB7 ${event.type}`,
75638
+ text: detailsToText(event)
75639
+ }
75640
+ }));
75641
+ }
75642
+ function aggregateToolStats(turns) {
75643
+ const stats = /* @__PURE__ */ new Map();
75644
+ for (const turn of turns) {
75645
+ for (const call of turn.toolCalls) {
75646
+ const stat2 = stats.get(call.toolName) ?? {
75647
+ name: call.toolName,
75648
+ calls: 0,
75649
+ errors: 0,
75650
+ totalMs: 0
75651
+ };
75652
+ stat2.calls += 1;
75653
+ if (call.status === "failed") stat2.errors += 1;
75654
+ if (call.endedAt !== void 0 && call.endedAt >= call.startedAt) {
75655
+ stat2.totalMs += call.endedAt - call.startedAt;
75656
+ }
75657
+ stats.set(call.toolName, stat2);
75658
+ }
75659
+ }
75660
+ return [...stats.values()].sort((a, b2) => b2.totalMs - a.totalMs || b2.calls - a.calls);
75661
+ }
75662
+ function buildToolStatRows(turns) {
75663
+ const stats = aggregateToolStats(turns);
75664
+ if (stats.length === 0) {
75665
+ return [{ id: "tools:none", label: "no tool calls in recent turns", estTokens: 0 }];
75666
+ }
75667
+ return stats.map((stat2) => ({
75668
+ id: `toolstat:${stat2.name}`,
75669
+ label: stat2.name,
75670
+ estTokens: 0,
75671
+ valueText: formatDurationMs(stat2.totalMs),
75672
+ note: `${stat2.calls} calls${stat2.errors > 0 ? ` \xB7 ${stat2.errors} errors` : ""}`,
75673
+ drill: {
75674
+ kind: "rows",
75675
+ title: `Tool Calls \xB7 ${stat2.name}`,
75676
+ rowKind: "plain",
75677
+ rows: collectCalls(turns, stat2.name)
75678
+ }
75679
+ }));
75680
+ }
75681
+ function collectCalls(turns, toolName) {
75682
+ const rows = [];
75683
+ for (const turn of turns) {
75684
+ for (const call of turn.toolCalls) {
75685
+ if (call.toolName !== toolName) continue;
75686
+ const duration = call.endedAt !== void 0 && call.endedAt >= call.startedAt ? formatDurationMs(call.endedAt - call.startedAt) : "running";
75687
+ rows.push({
75688
+ id: `call:${call.toolCallId ?? `${call.startedAt}:${rows.length}`}`,
75689
+ label: `${formatTime(call.startedAt)} ${call.status}`,
75690
+ estTokens: 0,
75691
+ valueText: duration,
75692
+ detail: call.resultSummary || void 0,
75693
+ marker: call.status === "failed" ? "\u2716" : void 0,
75694
+ drill: {
75695
+ kind: "text",
75696
+ title: `Call \xB7 ${toolName}`,
75697
+ text: [
75698
+ `# ${toolName} (${call.status})`,
75699
+ `started: ${new Date(call.startedAt).toISOString()}`,
75700
+ call.endedAt !== void 0 ? `ended: ${new Date(call.endedAt).toISOString()}` : "",
75701
+ call.outcome !== void 0 ? `
75702
+ # outcome
75703
+ ${safeStringify(call.outcome)}` : ""
75704
+ ].filter(Boolean).join("\n").slice(0, MAX_DRILL_CHARS)
75705
+ }
75706
+ });
75707
+ }
75708
+ }
75709
+ return rows.sort((a, b2) => a.id.localeCompare(b2.id));
75710
+ }
75711
+ function buildTurnRows(turns) {
75712
+ if (turns.length === 0) {
75713
+ return [{ id: "turns:none", label: "no completed turns recorded yet", estTokens: 0 }];
75714
+ }
75715
+ return [...turns].sort((a, b2) => b2.turnIndex - a.turnIndex).map((turn) => {
75716
+ const duration = turn.completedAt !== void 0 && turn.startedAt !== void 0 && turn.completedAt >= turn.startedAt ? formatDurationMs(turn.completedAt - turn.startedAt) : "\u2014";
75717
+ return {
75718
+ id: `turn:${turn.turnIndex}`,
75719
+ label: `#${turn.turnIndex} ${turn.state}`,
75720
+ estTokens: 0,
75721
+ valueText: duration,
75722
+ note: `${turn.toolCalls.length} tool calls`,
75723
+ detail: turn.userText?.slice(0, 80) || void 0,
75724
+ marker: turn.state === "failed" ? "\u2716" : void 0,
75725
+ drill: {
75726
+ kind: "text",
75727
+ title: `Turn #${turn.turnIndex}`,
75728
+ text: turnToText(turn)
75729
+ }
75730
+ };
75731
+ });
75732
+ }
75733
+ function turnToText(turn) {
75734
+ const parts = [
75735
+ `# turn ${turn.turnIndex} (${turn.state})`,
75736
+ turn.startedAt !== void 0 ? `started: ${new Date(turn.startedAt).toISOString()}` : "",
75737
+ turn.completedAt !== void 0 ? `completed: ${new Date(turn.completedAt).toISOString()}` : ""
75738
+ ];
75739
+ if (turn.userText) parts.push(`
75740
+ # user
75741
+ ${turn.userText.slice(0, 2e3)}`);
75742
+ if (turn.assistantText) parts.push(`
75743
+ # assistant
75744
+ ${turn.assistantText.slice(0, 2e3)}`);
75745
+ if (turn.errorMessage) parts.push(`
75746
+ # error
75747
+ ${turn.errorMessage}`);
75748
+ if (turn.toolCalls.length > 0) {
75749
+ parts.push(
75750
+ `
75751
+ # tool calls
75752
+ ${turn.toolCalls.map(
75753
+ (call) => `- ${call.toolName} (${call.status})${call.endedAt !== void 0 && call.endedAt >= call.startedAt ? ` ${formatDurationMs(call.endedAt - call.startedAt)}` : ""}${call.resultSummary ? ` \u2014 ${call.resultSummary.slice(0, 200)}` : ""}`
75754
+ ).join("\n")}`
75755
+ );
75756
+ }
75757
+ return parts.filter(Boolean).join("\n").slice(0, MAX_DRILL_CHARS);
75758
+ }
75759
+ function buildRuntimeSummaryRows(snapshot) {
75760
+ const summary = snapshot.runtimeSummary;
75761
+ if (!summary) {
75762
+ return [{ id: "runtime:none", label: "no runtime summary recorded yet", estTokens: 0 }];
75763
+ }
75764
+ const entries = [
75765
+ ["tool results", summary.toolResults, "completed tool calls (recent turns)"],
75766
+ ["tool errors", summary.toolErrors, "failed tool calls (recent turns)"],
75767
+ ["retries", summary.retryEvents, "provider/overflow retries"],
75768
+ ["auto-compactions", summary.autoCompactionEvents, "context checkpoints this session"],
75769
+ ["runtime guard events", summary.runtimeGuardEvents, "guard advisories triggered"],
75770
+ ["queued turn events", summary.queuedTurnEvents, "steer/followUp queue dispatches"],
75771
+ ["subagent events", summary.subagentEvents, "subagent lifecycle events"]
75772
+ ];
75773
+ return entries.map(([label, count, detail]) => ({
75774
+ id: `runtime:${label}`,
75775
+ label,
75776
+ estTokens: count,
75777
+ valueText: String(count),
75778
+ note: count > 0 ? void 0 : "none",
75779
+ detail
75780
+ }));
75781
+ }
75782
+ function buildSubagentRows(snapshot) {
75783
+ const summaries = snapshot.subagentSummaries ?? [];
75784
+ if (summaries.length === 0) {
75785
+ return [{ id: "subagents:none", label: "no subagent tasks recorded yet", estTokens: 0 }];
75786
+ }
75787
+ return summaries.map((sub, index) => ({
75788
+ id: `subagent:${sub.toolCallId ?? index}`,
75789
+ label: sub.name,
75790
+ estTokens: 0,
75791
+ valueText: sub.status,
75792
+ note: sub.modelId ? `${sub.modelClass ? `${sub.modelClass}/` : ""}${sub.modelId}` : void 0,
75793
+ detail: [
75794
+ sub.evidenceCount > 0 ? `${sub.evidenceCount} evidence` : "",
75795
+ sub.failureReason ? `failed: ${sub.failureReason}` : "",
75796
+ sub.modelFallbackReason ? `fallback: ${sub.modelFallbackReason}` : ""
75797
+ ].filter(Boolean).join(" \xB7 ") || void 0,
75798
+ marker: sub.status === "failed" || sub.failureReason ? "\u2716" : void 0,
75799
+ drill: {
75800
+ kind: "text",
75801
+ title: `Subagent \xB7 ${sub.name}`,
75802
+ text: safeStringify(sub).slice(0, MAX_DRILL_CHARS)
75803
+ }
75804
+ }));
75805
+ }
75806
+ function buildTraceSections(snapshot) {
75807
+ const turns = [...snapshot.recentTurns];
75808
+ if (snapshot.activeTurn) turns.push(snapshot.activeTurn);
75809
+ const timeline = snapshot.recentTimeline ?? [];
75810
+ return [
75811
+ {
75812
+ id: "trace-timeline",
75813
+ title: `Timeline (${timeline.length})`,
75814
+ estTokens: 0,
75815
+ rows: buildTimelineRows(timeline)
75816
+ },
75817
+ {
75818
+ id: "trace-tools",
75819
+ title: "Tool stats (recent turns)",
75820
+ estTokens: 0,
75821
+ note: aggregateToolStats(turns)[0] ? `slowest: ${aggregateToolStats(turns)[0].name} ${formatDurationMs(aggregateToolStats(turns)[0].totalMs)}` : void 0,
75822
+ rows: buildToolStatRows(turns)
75823
+ },
75824
+ {
75825
+ id: "trace-turns",
75826
+ title: `Recent turns (${snapshot.recentTurns.length}${snapshot.activeTurn ? " +1 active" : ""})`,
75827
+ estTokens: 0,
75828
+ rows: buildTurnRows(
75829
+ snapshot.activeTurn ? [...snapshot.recentTurns, snapshot.activeTurn] : snapshot.recentTurns
75830
+ )
75831
+ },
75832
+ {
75833
+ id: "trace-runtime",
75834
+ title: "Runtime summary",
75835
+ estTokens: 0,
75836
+ rows: buildRuntimeSummaryRows(snapshot)
75837
+ },
75838
+ {
75839
+ id: "trace-subagents",
75840
+ title: `Subagents (${(snapshot.subagentSummaries ?? []).length})`,
75841
+ estTokens: 0,
75842
+ rows: buildSubagentRows(snapshot)
75843
+ }
75844
+ ];
75845
+ }
75846
+ function buildTraceOverviewText(snapshot, theme2) {
75847
+ const t = theme2 ?? { bold: (s) => s, fg: (_k, s) => s };
75848
+ const lines = [];
75849
+ const turns = snapshot.recentTurns.length + (snapshot.activeTurn ? 1 : 0);
75850
+ const timeline = snapshot.recentTimeline ?? [];
75851
+ lines.push(t.bold("Session Trace") + t.fg("dim", ` \xB7 ${turns} recent turns \xB7 ${timeline.length} events`));
75852
+ const summary = snapshot.runtimeSummary;
75853
+ if (summary) {
75854
+ const notable = [];
75855
+ if (summary.retryEvents > 0) notable.push(`retries ${summary.retryEvents}`);
75856
+ if (summary.autoCompactionEvents > 0) notable.push(`compactions ${summary.autoCompactionEvents}`);
75857
+ if (summary.runtimeGuardEvents > 0) notable.push(`guard ${summary.runtimeGuardEvents}`);
75858
+ if (summary.toolErrors > 0) notable.push(`tool errors ${summary.toolErrors}`);
75859
+ if (notable.length > 0) {
75860
+ lines.push(t.fg("dim", ` events: ${notable.join(" \xB7 ")}`));
75861
+ } else {
75862
+ lines.push(t.fg("dim", " no retries, compactions, or guard events"));
75863
+ }
75864
+ }
75865
+ const stats = aggregateToolStats([...snapshot.recentTurns, ...snapshot.activeTurn ? [snapshot.activeTurn] : []]);
75866
+ if (stats.length > 0) {
75867
+ const top = stats.slice(0, 3).map((s) => `${s.name} ${formatDurationMs(s.totalMs)} (${s.calls})`);
75868
+ lines.push(t.fg("dim", ` tools: ${top.join(" \xB7 ")}`));
75869
+ }
75870
+ lines.push(t.fg("dim", " /debugs/trace \xB7 Enter to drill, Esc to go back"));
75871
+ return lines.join("\n");
75872
+ }
75873
+
75874
+ // src/commands/interactive-trace-command.ts
75875
+ function handleTraceCommand(host, snapshot) {
75876
+ if (!snapshot) {
75877
+ host.showStatus("No session trace yet \u2014 it starts with the first turn");
75878
+ return;
75879
+ }
75880
+ const sections = buildTraceSections(snapshot);
75881
+ host.chatContainer.addChild(new Spacer(1));
75882
+ host.chatContainer.addChild(new Text(buildTraceOverviewText(snapshot, theme), 1, 0));
75883
+ host.ui.requestRender();
75884
+ host.showSelector((done) => {
75885
+ const selector = new ContextReportSelectorComponent({
75886
+ title: "Session Trace",
75887
+ sections,
75888
+ terminalHeight: host.ui.terminal.rows,
75889
+ onDone: done
75890
+ });
75891
+ return { component: selector, focus: selector };
75892
+ });
75893
+ }
75894
+
75895
+ // src/core/boot-report.ts
75896
+ function stepToText(step) {
75897
+ const lines = [
75898
+ `# ${step.id}`,
75899
+ `input: ${step.inputSummary}`,
75900
+ `output: ${step.outputSummary}`
75901
+ ];
75902
+ if (step.toolCount !== void 0) lines.push(`tools: ${step.toolCount}`);
75903
+ if (step.policyDecisions?.length) {
75904
+ lines.push("", "# policy decisions", ...step.policyDecisions.map((decision) => `- ${decision}`));
75905
+ }
75906
+ if (step.extensionWrapping) {
75907
+ lines.push(
75908
+ "",
75909
+ "# extension wrapping",
75910
+ `extensions: ${step.extensionWrapping.extensionCount}`,
75911
+ `extension tools: ${step.extensionWrapping.extensionToolCount}`,
75912
+ `wrapped tools: ${step.extensionWrapping.wrappedToolCount}`
75913
+ );
75914
+ }
75915
+ return lines.join("\n");
75916
+ }
75917
+ function buildBootRows(trace) {
75918
+ if (trace.steps.length === 0) {
75919
+ return [{ id: "boot:none", label: "no composition steps recorded", estTokens: 0 }];
75920
+ }
75921
+ return trace.steps.map((step, index) => ({
75922
+ id: `boot:${step.id}`,
75923
+ label: `${index + 1}. ${step.id}`,
75924
+ estTokens: 0,
75925
+ note: step.toolCount !== void 0 ? `${step.toolCount} tools` : void 0,
75926
+ detail: step.outputSummary,
75927
+ drill: {
75928
+ kind: "text",
75929
+ title: `Step \xB7 ${step.id}`,
75930
+ text: stepToText(step)
75931
+ }
75932
+ }));
75933
+ }
75934
+ function buildBootSections(trace) {
75935
+ return [
75936
+ {
75937
+ id: "boot-pipeline",
75938
+ title: `Composition pipeline (${trace.steps.length} steps)`,
75939
+ estTokens: 0,
75940
+ rows: buildBootRows(trace)
75941
+ }
75942
+ ];
75943
+ }
75944
+ function buildBootOverviewText(trace, theme2) {
75945
+ const t = theme2 ?? { bold: (s) => s, fg: (_k, s) => s };
75946
+ const lines = [];
75947
+ lines.push(t.bold("Boot Composition") + t.fg("dim", ` \xB7 ${trace.steps.length} steps`));
75948
+ for (const step of trace.steps) {
75949
+ lines.push(t.fg("dim", ` ${step.id} \u2014 ${step.outputSummary}`));
75950
+ }
75951
+ lines.push(t.fg("dim", " /debugs/boot \xB7 Enter to drill into a step"));
75952
+ return lines.join("\n");
75953
+ }
75954
+
75955
+ // src/commands/interactive-boot-command.ts
75956
+ function handleBootCommand(host) {
75957
+ const trace = host.options.compositionTrace;
75958
+ if (!trace || trace.steps.length === 0) {
75959
+ host.showStatus("No composition trace available for this runtime");
75960
+ return;
75961
+ }
75962
+ const sections = buildBootSections(trace);
75963
+ host.chatContainer.addChild(new Spacer(1));
75964
+ host.chatContainer.addChild(new Text(buildBootOverviewText(trace, theme), 1, 0));
75965
+ host.ui.requestRender();
75966
+ host.showSelector((done) => {
75967
+ const selector = new ContextReportSelectorComponent({
75968
+ title: "Boot Composition",
75969
+ sections,
75970
+ terminalHeight: host.ui.terminal.rows,
75971
+ onDone: done
75972
+ });
75973
+ return { component: selector, focus: selector };
75974
+ });
75975
+ }
75976
+
74656
75977
  // src/commands/interactive-command-handlers.ts
74657
75978
  function showSettingsSelector(host, focusItem) {
74658
75979
  host.showSelector((done) => {
@@ -75077,6 +76398,7 @@ async function handleClearCommand(host) {
75077
76398
  contextPercent: contextWindow > 0 && contextUsage.tokens !== null ? contextUsage.tokens / contextWindow * 100 : 0,
75078
76399
  contextWindow
75079
76400
  });
76401
+ applyContextSignalsToFooter(host);
75080
76402
  host.updateTerminalTitle();
75081
76403
  host.chatContainer.clear();
75082
76404
  host.streamingComponent = void 0;
@@ -75123,6 +76445,7 @@ function handleSessionCommand(host) {
75123
76445
  homeDir: process.env.HOME || process.env.USERPROFILE || "",
75124
76446
  modelId: host.options.model?.id,
75125
76447
  thinkingLevel: host.thinkingLevel,
76448
+ messages: host.sessionManager.getMessages(),
75126
76449
  theme
75127
76450
  });
75128
76451
  host.chatContainer.addChild(new Spacer(1));
@@ -75356,12 +76679,16 @@ function registerBuiltinCommands(host) {
75356
76679
  handler: () => host.handleCacheTest()
75357
76680
  });
75358
76681
  commandTree.register(["debugs", "context"], {
75359
- description: "Append a context summary to ~/.pie/logs/pie-cli.log",
75360
- handler: () => host.handleDebugContextCommand()
76682
+ description: "Context composition report: sections, tokens, drill-down",
76683
+ handler: () => host.handleDebugReportCommand("context")
76684
+ });
76685
+ commandTree.register(["debugs", "trace"], {
76686
+ description: "Session trace: timeline, tool stats, runtime events",
76687
+ handler: () => host.handleDebugReportCommand("trace")
75361
76688
  });
75362
- commandTree.register(["debugs", "package"], {
75363
- description: "Append a package summary to ~/.pie/logs/pie-cli.log",
75364
- handler: () => host.handleDebugPackageCommand()
76689
+ commandTree.register(["debugs", "boot"], {
76690
+ description: "Boot composition pipeline: how the runtime was assembled",
76691
+ handler: () => host.handleDebugReportCommand("boot")
75365
76692
  });
75366
76693
  commandTree.register(["settings", "quit"], {
75367
76694
  description: "Exit Pie",
@@ -75372,6 +76699,36 @@ function registerBuiltinCommands(host) {
75372
76699
  });
75373
76700
  }
75374
76701
 
76702
+ // src/runtime/compaction-diff.ts
76703
+ function summarizeActiveContext(summaries) {
76704
+ let tokens = 0;
76705
+ for (const summary of summaries) {
76706
+ tokens += summary.estTokens;
76707
+ }
76708
+ return { count: summaries.length, tokens };
76709
+ }
76710
+ function formatCompactionTokenLine(stats) {
76711
+ const saved = Math.max(0, stats.before.tokens - stats.after.tokens);
76712
+ const percent = stats.before.tokens > 0 ? Math.round(saved / stats.before.tokens * 100) : 0;
76713
+ return `~${formatReportTokens(stats.before.tokens)} \u2192 ~${formatReportTokens(stats.after.tokens)} tokens (saved ~${formatReportTokens(saved)}, \u2212${percent}%)`;
76714
+ }
76715
+ function renderCompactionDiffPanel(target, stats) {
76716
+ const archived = Math.max(0, stats.before.count - stats.after.count);
76717
+ target.chatContainer.addChild(new Spacer(1));
76718
+ target.chatContainer.addChild(new Text(theme.fg("accent", "\u2691 Context checkpoint"), 1, 0));
76719
+ target.chatContainer.addChild(
76720
+ new Text(
76721
+ theme.fg(
76722
+ "dim",
76723
+ ` kept ${stats.after.count} entries \xB7 archived ${archived} entries \xB7 ${formatCompactionTokenLine(stats)}`
76724
+ ),
76725
+ 1,
76726
+ 0
76727
+ )
76728
+ );
76729
+ target.safeRequestRender();
76730
+ }
76731
+
75375
76732
  // src/commands/interactive-compact-command.ts
75376
76733
  async function handleCompactCommand(host) {
75377
76734
  if (host.submitLock || host.isProcessing || host.isCompacting) {
@@ -75390,6 +76747,7 @@ async function handleCompactCommand(host) {
75390
76747
  host.chatContainer.addChild(new Spacer(1));
75391
76748
  host.chatContainer.addChild(new Text(theme.fg("dim", "[/sessions/compact]"), 1, 0));
75392
76749
  host.safeRequestRender();
76750
+ const beforeStats = summarizeActiveContext(host.sessionManager.getActiveContextSummaries());
75393
76751
  try {
75394
76752
  const activeEntryId = host.sessionManager.getActiveEntryId();
75395
76753
  if (!activeEntryId) {
@@ -75432,8 +76790,10 @@ async function handleCompactCommand(host) {
75432
76790
  host.lastStatusSpacer = void 0;
75433
76791
  host.lastStatusText = void 0;
75434
76792
  host.renderMessageHistory(compactedMessages);
75435
- host.chatContainer.addChild(new Spacer(1));
75436
- host.chatContainer.addChild(new Text(theme.fg("accent", "Context compacted"), 1, 0));
76793
+ renderCompactionDiffPanel(host, {
76794
+ before: beforeStats,
76795
+ after: summarizeActiveContext(host.sessionManager.getActiveContextSummaries())
76796
+ });
75437
76797
  const contextUsage = host.calculateContextUsage();
75438
76798
  const contextWindow = host.options.model?.contextWindow ?? 0;
75439
76799
  const contextPercent = contextWindow > 0 && contextUsage.tokens !== null ? contextUsage.tokens / contextWindow * 100 : 0;
@@ -75448,6 +76808,7 @@ async function handleCompactCommand(host) {
75448
76808
  contextPercent,
75449
76809
  contextWindow
75450
76810
  });
76811
+ applyContextSignalsToFooter(host);
75451
76812
  await host.sessionManager.save();
75452
76813
  host.safeRequestRender();
75453
76814
  } catch (error) {
@@ -77824,6 +79185,14 @@ var FooterComponent = class {
77824
79185
  if (state.cacheRead) statsParts.push(`R${formatTokens(state.cacheRead)}`);
77825
79186
  if (state.cacheWrite) statsParts.push(`W${formatTokens(state.cacheWrite)}`);
77826
79187
  if (state.cost) statsParts.push(`$${state.cost.toFixed(3)}`);
79188
+ if (state.cacheHitRate != null) {
79189
+ statsParts.push(theme.fg("dim", `ca ${(state.cacheHitRate * 100).toFixed(0)}%`));
79190
+ }
79191
+ if (state.prefixHash) {
79192
+ statsParts.push(
79193
+ state.prefixHashChanged ? theme.fg("warning", `\u26A1${state.prefixHash}`) : theme.fg("dim", `\u2301${state.prefixHash}`)
79194
+ );
79195
+ }
77827
79196
  if (state.contextWindow) {
77828
79197
  const autoIndicator = this.autoCompactEnabled ? " (auto)" : "";
77829
79198
  const percent = state.contextPercent ?? 0;
@@ -77896,27 +79265,13 @@ var FooterComponent = class {
77896
79265
  // src/components/todo-widget.ts
77897
79266
  var TodoWidgetComponent = class extends Container {
77898
79267
  state;
77899
- calmMode = false;
77900
79268
  constructor(state) {
77901
79269
  super();
77902
79270
  this.state = state;
77903
79271
  void this.refresh();
77904
79272
  }
77905
- /** Calm mode hides the progress widget (questions/answers only). */
77906
- setCalmMode(calm) {
77907
- if (this.calmMode === calm) return;
77908
- this.calmMode = calm;
77909
- if (calm) {
77910
- this.clear();
77911
- } else {
77912
- void this.refresh();
77913
- }
77914
- }
77915
79273
  async refresh() {
77916
79274
  this.clear();
77917
- if (this.calmMode) {
77918
- return;
77919
- }
77920
79275
  const lines = renderExecutionWidgetLines(this.state.getWidgetSteps());
77921
79276
  if (lines.length === 0) {
77922
79277
  return;
@@ -78907,6 +80262,7 @@ function createInteractiveAgentSessionController(host) {
78907
80262
  }
78908
80263
  let retryElapsedTicker;
78909
80264
  let retryElapsedStartedAt = 0;
80265
+ let autoCompactBeforeStats = null;
78910
80266
  const attention = cliAttention(host);
78911
80267
  const stopRetryElapsedTicker = () => {
78912
80268
  if (retryElapsedTicker !== void 0) {
@@ -78972,6 +80328,7 @@ function createInteractiveAgentSessionController(host) {
78972
80328
  if (!firstKeptEntryId) return { aborted: true };
78973
80329
  return { summary: beforeCompact.summary, firstKeptEntryId };
78974
80330
  }
80331
+ autoCompactBeforeStats = summarizeActiveContext(context.sessionManager.getActiveContextSummaries());
78975
80332
  return createCliAutoCompactionResult({
78976
80333
  context,
78977
80334
  model: host.options.model,
@@ -79140,6 +80497,7 @@ function createInteractiveAgentSessionController(host) {
79140
80497
  host.showError(`${prefix}: ${event.errorMessage}`);
79141
80498
  host.footer.setState({ status: "ready" });
79142
80499
  host.setControllerBusy(false);
80500
+ autoCompactBeforeStats = null;
79143
80501
  return;
79144
80502
  }
79145
80503
  host.resetFileObservationState();
@@ -79165,10 +80523,20 @@ function createInteractiveAgentSessionController(host) {
79165
80523
  contextPercent,
79166
80524
  contextWindow
79167
80525
  });
80526
+ applyContextSignalsToFooter(host);
79168
80527
  host.safeRequestRender();
79169
- if (!event.willRetry && !event.result?.aborted) {
79170
- host.showStatus("Context auto-compacted");
80528
+ if (!event.result?.aborted) {
80529
+ if (autoCompactBeforeStats !== null) {
80530
+ renderCompactionDiffPanel(host, {
80531
+ before: autoCompactBeforeStats,
80532
+ after: summarizeActiveContext(host.sessionManager.getActiveContextSummaries())
80533
+ });
80534
+ }
80535
+ if (!event.willRetry) {
80536
+ host.showStatus("Context auto-compacted");
80537
+ }
79171
80538
  }
80539
+ autoCompactBeforeStats = null;
79172
80540
  } else if (event.type === "auto_continue_scheduled") {
79173
80541
  host.setControllerBusy(true);
79174
80542
  if (event.reason === "todo_closure") {
@@ -80606,7 +81974,7 @@ var InteractiveMode = class {
80606
81974
  async init() {
80607
81975
  const activeSession = this.sessionManager.getActiveSession();
80608
81976
  this.thinkingLevel = resolveInitialThinkingLevel({ session: activeSession, settingsManager: this.options.settingsManager });
80609
- const { Agent } = await import("./chunks/src-HPIBCE2I.js");
81977
+ const { Agent } = await import("./chunks/src-OWQI5M6W.js");
80610
81978
  this.agent = new Agent({
80611
81979
  initialState: {
80612
81980
  systemPrompt: this.buildSystemPrompt(),
@@ -80832,7 +82200,6 @@ var InteractiveMode = class {
80832
82200
  this.options.settingsManager.setCalmModeEnabled(enabled);
80833
82201
  this.footer.setState({ calmMode: enabled });
80834
82202
  applyCalmMode(this, enabled);
80835
- this.todoWidget.setCalmMode(enabled);
80836
82203
  this.extensionWidgets.setCalmMode(enabled);
80837
82204
  this.showStatus(`Calm mode ${enabled ? "on \u2014 showing questions and final answers only" : "off"}`);
80838
82205
  this.safeRequestRender(true);
@@ -80870,11 +82237,11 @@ var InteractiveMode = class {
80870
82237
  async handleCacheTest() {
80871
82238
  return handleCacheTest(this);
80872
82239
  }
80873
- async handleDebugContextCommand() {
80874
- return handleDebugContextCommand(this);
82240
+ async handleTreeCommand() {
82241
+ return handleTreeCommand(this);
80875
82242
  }
80876
- async handleDebugPackageCommand() {
80877
- return handleDebugPackageCommand(this);
82243
+ handleDebugReportCommand(kind) {
82244
+ return kind === "context" ? handleContextReportCommand(this) : kind === "trace" ? handleTraceCommand(this, this.sessionTrace.getLiveSnapshot()) : handleBootCommand(this);
80878
82245
  }
80879
82246
  async handleResumeCommand() {
80880
82247
  return handleResumeCommand(this);
@@ -80894,9 +82261,6 @@ var InteractiveMode = class {
80894
82261
  setWaitingForUser(waiting) {
80895
82262
  return setWaitingForUser(this, waiting);
80896
82263
  }
80897
- async handleTreeCommand() {
80898
- return handleTreeCommand(this);
80899
- }
80900
82264
  async forkAtEntry(entryId) {
80901
82265
  return forkAtEntry(this, entryId);
80902
82266
  }
@@ -81441,7 +82805,7 @@ async function runPrintMode(params) {
81441
82805
  `);
81442
82806
  console.log("Assistant: ");
81443
82807
  }
81444
- const { Agent: AgentClass } = await import("./chunks/src-HPIBCE2I.js");
82808
+ const { Agent: AgentClass } = await import("./chunks/src-OWQI5M6W.js");
81445
82809
  let agent;
81446
82810
  const printModeExtensions = await loadPrintModeExtensions({
81447
82811
  cwd: runtime.cwd,
@@ -82302,7 +83666,7 @@ async function runJsonEventsMode(params) {
82302
83666
  return;
82303
83667
  }
82304
83668
  const parent = agent;
82305
- const { Agent: AgentClass } = await import("./chunks/src-HPIBCE2I.js");
83669
+ const { Agent: AgentClass } = await import("./chunks/src-OWQI5M6W.js");
82306
83670
  const sideAgent = new AgentClass({
82307
83671
  initialState: {
82308
83672
  systemPrompt: parent.state.systemPrompt,
@@ -82646,7 +84010,7 @@ async function runJsonEventsMode(params) {
82646
84010
  if (runtime.initialModel.unconfigured || controller) {
82647
84011
  return;
82648
84012
  }
82649
- const { Agent: AgentClass } = await import("./chunks/src-HPIBCE2I.js");
84013
+ const { Agent: AgentClass } = await import("./chunks/src-OWQI5M6W.js");
82650
84014
  const jsonModeExtensions = await loadJsonModeExtensions({ runtime, getAgent: () => agent });
82651
84015
  extensionRunner = jsonModeExtensions.runner;
82652
84016
  const tools = [...runtime.tools, ...jsonModeExtensions.extensionTools];