@creative-dswork/dscode 0.2.2 → 0.2.4

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/dscode.mjs CHANGED
@@ -214348,7 +214348,7 @@ function generateRecoveryTimelineHTML(recoveryArcs) {
214348
214348
  <span style="background:${COLORS.warn};color:#000;padding:4px 10px;border-radius:4px;font-size:12px;font-weight:600;white-space:nowrap;">\u{1F50D} Step ${arc.detectionStep}</span>
214349
214349
  <span style="color:${COLORS.textMuted};font-size:14px;">\u2192</span>
214350
214350
  <span style="background:${effectiveColor};color:#000;padding:4px 10px;border-radius:4px;font-size:12px;font-weight:600;white-space:nowrap;">${effectiveIcon} Step ${arc.correctionStep}</span>
214351
- <span style="background:rgba(88,166,255,0.15);padding:2px 8px;border-radius:4px;font-size:11px;color:${COLORS.accent};">${detLabel}</span>
214351
+ <span style="background:rgba(212,151,8,0.15);padding:2px 8px;border-radius:4px;font-size:11px;color:${COLORS.accent};">${detLabel}</span>
214352
214352
  <span style="font-size:11px;color:${COLORS.textMuted};">${arc.stepsToRecover} steps</span>
214353
214353
  ${arc.misdiagnosisCount > 0 ? `<span style="background:rgba(210,153,29,0.15);padding:2px 8px;border-radius:4px;font-size:11px;color:${COLORS.warn};">${arc.misdiagnosisCount} \u6B21\u8BEF\u5224</span>` : ""}
214354
214354
  </div>
@@ -214356,7 +214356,7 @@ function generateRecoveryTimelineHTML(recoveryArcs) {
214356
214356
  <span style="color:${COLORS.danger};">${escapeHtml(arc.errorAgent)}</span>: ${escapeHtml(arc.errorSummary)}
214357
214357
  \u2192 <span style="color:${effectiveColor};">${escapeHtml(arc.correctionAgent)}</span>: ${escapeHtml(arc.correctionSummary)}
214358
214358
  </div>
214359
- <div style="font-size:12px;color:${COLORS.accent};font-style:italic;padding:8px 12px;background:rgba(88,166,255,0.08);border-radius:4px;border-left:3px solid ${COLORS.accent};margin-top:8px;">
214359
+ <div style="font-size:12px;color:${COLORS.accent};font-style:italic;padding:8px 12px;background:rgba(212,151,8,0.08);border-radius:4px;border-left:3px solid ${COLORS.accent};margin-top:8px;">
214360
214360
  \u{1F4A1} \u6839\u56E0\u5047\u8BF4: ${escapeHtml(arc.rootCauseHypothesis)}
214361
214361
  </div>
214362
214362
  </div>`;
@@ -214444,7 +214444,7 @@ function generateRuleChainHTML(attribution, rulesApplied) {
214444
214444
  "Rule3": "Irrecoverable Point \u2014 identifies the FIRST step that made the correct path unrecoverable (not necessarily the first error)"
214445
214445
  };
214446
214446
  const ruleItems = rulesApplied.map((r) => `
214447
- <div style="padding:8px 12px;margin:4px 0;background:${r === "Rule3" ? "rgba(248,81,73,0.15)" : "rgba(88,166,255,0.1)"};border-left:3px solid ${r === "Rule3" ? COLORS.danger : COLORS.accent};border-radius:4px;">
214447
+ <div style="padding:8px 12px;margin:4px 0;background:${r === "Rule3" ? "rgba(224,85,83,0.15)" : "rgba(212,151,8,0.1)"};border-left:3px solid ${r === "Rule3" ? COLORS.danger : COLORS.accent};border-radius:4px;">
214448
214448
  <strong style="color:${COLORS.accent};font-size:13px;">${escapeHtml(r)}</strong>
214449
214449
  <div style="font-size:12px;color:${COLORS.textMuted};margin-top:2px;">${escapeHtml(ruleDescriptions[r] ?? "")}</div>
214450
214450
  </div>`).join("");
@@ -214471,7 +214471,7 @@ function generateCascadePathHTML(cascadePath) {
214471
214471
  <span style="font-family:monospace;font-size:13px;color:${COLORS.accent};">${escapeHtml(e.fromZoneId)}:${e.fromStepId}</span>
214472
214472
  <span style="color:${COLORS.textMuted};font-size:18px;">\u2192</span>
214473
214473
  <span style="font-family:monospace;font-size:13px;">${escapeHtml(e.toZoneId)}:${e.toStepId}</span>
214474
- <span style="background:rgba(88,166,255,0.15);padding:2px 8px;border-radius:4px;font-size:11px;color:${COLORS.accent};">${mechanismLabel}</span>
214474
+ <span style="background:rgba(212,151,8,0.15);padding:2px 8px;border-radius:4px;font-size:11px;color:${COLORS.accent};">${mechanismLabel}</span>
214475
214475
  <span style="font-size:11px;color:${COLORS.textMuted};">\u6570\u636E: ${escapeHtml(e.dataItem)}</span>
214476
214476
  </div>`;
214477
214477
  }).join("");
@@ -214503,9 +214503,9 @@ body {
214503
214503
  max-width: 1200px;
214504
214504
  margin: 0 auto;
214505
214505
  }
214506
- h1 { font-size: 28px; font-weight: 600; margin-bottom: 4px; color: #f0f6fc; }
214507
- h2 { font-size: 20px; font-weight: 600; margin: 32px 0 16px; color: #f0f6fc; border-bottom: 1px solid ${COLORS.border}; padding-bottom: 8px; }
214508
- h3 { font-size: 16px; font-weight: 600; margin-bottom: 8px; color: #e6edf3; }
214506
+ h1 { font-size: 28px; font-weight: 600; margin-bottom: 4px; color: #e8e4dd; }
214507
+ h2 { font-size: 20px; font-weight: 600; margin: 32px 0 16px; color: #e8e4dd; border-bottom: 1px solid ${COLORS.border}; padding-bottom: 8px; }
214508
+ h3 { font-size: 16px; font-weight: 600; margin-bottom: 8px; color: #e8e4dd; }
214509
214509
  .header { background: ${COLORS.card}; border: 1px solid ${COLORS.border}; border-radius: 8px; padding: 24px; margin-bottom: 24px; }
214510
214510
  .header-meta { display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); gap: 12px; margin-top: 16px; }
214511
214511
  .meta-item { }
@@ -214686,7 +214686,7 @@ ${(() => {
214686
214686
  grouped[cat].push(r);
214687
214687
  }
214688
214688
  return Object.entries(grouped).map(([cat, catRules]) => `
214689
- <h3 style="font-size:15px;font-weight:600;margin:24px 0 12px;color:#e6edf3;">${escapeHtml(catLabels[cat] ?? cat)}</h3>
214689
+ <h3 style="font-size:15px;font-weight:600;margin:24px 0 12px;color:#e8e4dd;">${escapeHtml(catLabels[cat] ?? cat)}</h3>
214690
214690
  ${catRules.map((r) => {
214691
214691
  const sevLabel = r.severity >= 1 ? "ERROR" : r.severity >= 0.6 ? "WARN" : "INFO";
214692
214692
  const sevColor = r.severity >= 1 ? COLORS.danger : r.severity >= 0.6 ? COLORS.warn : COLORS.accent;
@@ -214707,7 +214707,7 @@ ${catRules.map((r) => {
214707
214707
  ${r.rawDescription ? `
214708
214708
  <details style="margin-bottom:8px;">
214709
214709
  <summary style="font-size:11px;color:${COLORS.accent};cursor:pointer;">\u{1F4DD} \u8BE6\u7EC6\u63CF\u8FF0</summary>
214710
- <p style="font-size:12px;color:${COLORS.textMuted};margin:4px 0;padding:8px;background:rgba(88,166,255,0.05);border-radius:4px;white-space:pre-wrap;">${escapeHtml(r.rawDescription)}</p>
214710
+ <p style="font-size:12px;color:${COLORS.textMuted};margin:4px 0;padding:8px;background:rgba(212,151,8,0.05);border-radius:4px;white-space:pre-wrap;">${escapeHtml(r.rawDescription)}</p>
214711
214711
  </details>` : ""}
214712
214712
  ${r.severity >= 1 ? `<div style="font-size:11px;color:${COLORS.danger};margin-bottom:6px;">\u26A0 \u5EFA\u8BAE\u6301\u4E45\u5316\u5230 Agent \u914D\u7F6E (${escapeHtml(r.targetLayer)})</div>` : ""}
214713
214713
  <div style="background:${COLORS.card};border-radius:4px;padding:10px 12px;">
@@ -214771,15 +214771,15 @@ var init_dashboard = __esm({
214771
214771
  "src/eval/dashboard.ts"() {
214772
214772
  "use strict";
214773
214773
  COLORS = {
214774
- ok: "#3fb950",
214775
- warn: "#d2991d",
214776
- danger: "#f85149",
214777
- bg: "#0d1117",
214778
- card: "#161b22",
214779
- border: "#30363d",
214780
- text: "#c9d1d9",
214781
- textMuted: "#8b949e",
214782
- accent: "#58a6ff"
214774
+ ok: "#5ca860",
214775
+ warn: "#d4a017",
214776
+ danger: "#e05553",
214777
+ bg: "#1e1c19",
214778
+ card: "#282622",
214779
+ border: "#3a3732",
214780
+ text: "#e8e4dd",
214781
+ textMuted: "#8a8580",
214782
+ accent: "#d49708"
214783
214783
  };
214784
214784
  CASCADE_MECHANISM_LABELS = {
214785
214785
  data_contamination: "\u6570\u636E\u6C61\u67D3",
@@ -220426,6 +220426,83 @@ var init_at_file_resolver = __esm({
220426
220426
  }
220427
220427
  });
220428
220428
 
220429
+ // src/ui/shared/tool-result-formatter.ts
220430
+ function extractWriteSummary(text) {
220431
+ const lines = text.split("\n");
220432
+ const meaningful = [];
220433
+ for (const line of lines) {
220434
+ if (meaningful.length >= 2) break;
220435
+ const trimmed = line.trim();
220436
+ if (trimmed.length > 0) {
220437
+ meaningful.push(trimmed);
220438
+ }
220439
+ }
220440
+ if (meaningful.length === 0) return text.slice(0, DEFAULT_MAX_CHARS);
220441
+ const summary = meaningful.join("\n");
220442
+ const remainingLines = lines.length - 2;
220443
+ if (remainingLines > 0) {
220444
+ return `${summary}
220445
+ \u2026 (${remainingLines} more lines \u2014 anchor preview hidden)`;
220446
+ }
220447
+ return summary;
220448
+ }
220449
+ function isJSON(text) {
220450
+ const trimmed = text.trim();
220451
+ if (!trimmed.startsWith("{") && !trimmed.startsWith("[")) return false;
220452
+ try {
220453
+ JSON.parse(trimmed);
220454
+ return true;
220455
+ } catch {
220456
+ return false;
220457
+ }
220458
+ }
220459
+ function prettyPrintJSON(text) {
220460
+ return JSON.stringify(JSON.parse(text.trim()), null, 2);
220461
+ }
220462
+ function formatCodeBlock(text, lang) {
220463
+ const fence = lang ? "```" + lang : "```";
220464
+ return `${fence}
220465
+ ${text}
220466
+ \`\`\``;
220467
+ }
220468
+ function truncate(text) {
220469
+ if (text.length <= DEFAULT_MAX_CHARS) return text;
220470
+ return text.slice(0, DEFAULT_MAX_CHARS) + `
220471
+ \u2026 (${text.length - DEFAULT_MAX_CHARS} more chars)`;
220472
+ }
220473
+ function formatToolResultForUI(toolName, rawText) {
220474
+ if (!rawText) return "";
220475
+ switch (toolName) {
220476
+ case "write_file":
220477
+ case "overwrite_file":
220478
+ return extractWriteSummary(rawText);
220479
+ case "bash":
220480
+ return formatCodeBlock(truncate(rawText), "sh");
220481
+ case "grep":
220482
+ case "glob": {
220483
+ if (isJSON(rawText)) {
220484
+ return formatCodeBlock(truncate(prettyPrintJSON(rawText)), "json");
220485
+ }
220486
+ return formatCodeBlock(truncate(rawText));
220487
+ }
220488
+ case "read_file":
220489
+ return formatCodeBlock(truncate(rawText));
220490
+ default: {
220491
+ if (isJSON(rawText)) {
220492
+ return formatCodeBlock(truncate(prettyPrintJSON(rawText)), "json");
220493
+ }
220494
+ return truncate(rawText);
220495
+ }
220496
+ }
220497
+ }
220498
+ var DEFAULT_MAX_CHARS;
220499
+ var init_tool_result_formatter = __esm({
220500
+ "src/ui/shared/tool-result-formatter.ts"() {
220501
+ "use strict";
220502
+ DEFAULT_MAX_CHARS = 600;
220503
+ }
220504
+ });
220505
+
220429
220506
  // src/session/display.ts
220430
220507
  function extractText(content) {
220431
220508
  if (typeof content === "string") return content;
@@ -220449,9 +220526,10 @@ function extractThinkingFromContent(blocks) {
220449
220526
  }
220450
220527
  return parts.length > 0 ? parts.join("\n\n") : void 0;
220451
220528
  }
220452
- function extractToolResultText(blocks) {
220529
+ function extractToolResultText(blocks, toolName) {
220453
220530
  if (!Array.isArray(blocks)) return "";
220454
- return blocks.filter((b) => b && b.type === "text").map((b) => b.text).join("\n");
220531
+ const raw = blocks.filter((b) => b && b.type === "text").map((b) => b.text).join("\n");
220532
+ return formatToolResultForUI(toolName, raw);
220455
220533
  }
220456
220534
  function rebuildDisplayMessages(messages, visionMessages) {
220457
220535
  const visionMap = /* @__PURE__ */ new Map();
@@ -220494,14 +220572,17 @@ function rebuildDisplayMessages(messages, visionMessages) {
220494
220572
  const toolCallId = m.toolCallId;
220495
220573
  if (toolCallId && lastAssistantToolIds.has(toolCallId)) {
220496
220574
  const toolIdx = lastAssistantToolIds.get(toolCallId);
220497
- const resultText = extractToolResultText(m.content);
220575
+ const assistantMsg = messages[lastAssistantIdx];
220576
+ const parsedTools = assistantMsg.__parsedTools;
220577
+ const toolName = parsedTools?.[toolIdx]?.name ?? "unknown";
220578
+ const resultText = extractToolResultText(m.content, toolName);
220498
220579
  const isError = !!m.isError;
220499
220580
  let results = pendingResults.get(lastAssistantIdx);
220500
220581
  if (!results) {
220501
220582
  results = /* @__PURE__ */ new Map();
220502
220583
  pendingResults.set(lastAssistantIdx, results);
220503
220584
  }
220504
- results.set(toolCallId, { result: resultText, isError });
220585
+ results.set(toolCallId, { result: resultText, isError, toolName });
220505
220586
  } else {
220506
220587
  output.add(i);
220507
220588
  }
@@ -220577,6 +220658,7 @@ var init_display = __esm({
220577
220658
  "src/session/display.ts"() {
220578
220659
  "use strict";
220579
220660
  init_image_cache();
220661
+ init_tool_result_formatter();
220580
220662
  }
220581
220663
  });
220582
220664
 
@@ -220682,6 +220764,7 @@ import { createServer, request as httpRequest2 } from "node:http";
220682
220764
  import { readFileSync as readFileSync23, existsSync as existsSync25 } from "node:fs";
220683
220765
  import { join as join28, extname as extname2, resolve as resolve10 } from "node:path";
220684
220766
  import { fileURLToPath as fileURLToPath3 } from "node:url";
220767
+ import { streamSimple as streamSimple3 } from "@mariozechner/pi-ai";
220685
220768
  function extractImagesFromToolResult(result) {
220686
220769
  if (!result || typeof result !== "object") return void 0;
220687
220770
  const r = result;
@@ -220707,6 +220790,7 @@ var init_web_backend = __esm({
220707
220790
  init_mcp_browser();
220708
220791
  init_at_file_resolver();
220709
220792
  init_display();
220793
+ init_tool_result_formatter();
220710
220794
  init_ws_server();
220711
220795
  WebUiBackend = class {
220712
220796
  port;
@@ -220730,6 +220814,11 @@ var init_web_backend = __esm({
220730
220814
  pendingImages = [];
220731
220815
  // Message accumulation for the current assistant turn
220732
220816
  currentAssistant = null;
220817
+ // Context window broadcast throttling
220818
+ contextWindowThrottleTimer = null;
220819
+ contextWindowThrottlePending = false;
220820
+ lastArtifactHtml = "";
220821
+ isAssistantTurn = false;
220733
220822
  constructor(options) {
220734
220823
  this.port = options.port;
220735
220824
  this.harness = options.harness;
@@ -220758,23 +220847,29 @@ var init_web_backend = __esm({
220758
220847
  this.broadcast({ type: "tool_start", name: e.name, args: e.args });
220759
220848
  });
220760
220849
  h.events.on("tool:end", (e) => {
220761
- const rs = typeof e.result === "string" ? e.result.slice(0, 5e3) : JSON.stringify(e.result).slice(0, 5e3);
220850
+ const rawResult = typeof e.result === "string" ? e.result : JSON.stringify(e.result);
220851
+ const rs = formatToolResultForUI(e.name, rawResult);
220762
220852
  const imgs = extractImagesFromToolResult(e.result);
220763
220853
  if (this.currentAssistant) {
220764
220854
  this.currentAssistant.tools = this.currentAssistant.tools.filter((t) => t.name !== e.name || t.result !== "");
220765
220855
  this.currentAssistant.tools.push({ name: e.name, args: "", result: rs, isError: e.isError, images: imgs });
220766
220856
  }
220767
220857
  this.broadcast({ type: "tool_end", name: e.name, result: rs, isError: e.isError, images: imgs });
220858
+ this.broadcastContextWindow(false);
220768
220859
  });
220769
220860
  h.events.on("turn:streaming:start", () => {
220770
220861
  this.currentAssistant = { thinking: "", text: "", tools: [] };
220771
220862
  this.broadcast({ type: "assistant_start" });
220772
220863
  this.startSessionTimeBroadcast();
220864
+ this.isAssistantTurn = true;
220773
220865
  });
220774
220866
  h.events.on("turn:end", () => {
220775
220867
  this.broadcastSessionTime();
220868
+ const toolsForBroadcast = this.currentAssistant?.tools ?? [];
220776
220869
  this.currentAssistant = null;
220777
220870
  this.broadcast({ type: "assistant_end" });
220871
+ this.isAssistantTurn = false;
220872
+ this.broadcastContextWindow(true, toolsForBroadcast);
220778
220873
  this.pushSessionListToAll();
220779
220874
  });
220780
220875
  h.events.on("turn:abort", () => {
@@ -220810,6 +220905,7 @@ var init_web_backend = __esm({
220810
220905
  this.currentAssistant = null;
220811
220906
  this.pendingImages = [];
220812
220907
  this.broadcast({ type: "clear_conversation" });
220908
+ this.broadcastContextWindow(true);
220813
220909
  });
220814
220910
  h.events.on("config:change", (e) => {
220815
220911
  this.broadcast({ type: "config", data: e.data });
@@ -220827,6 +220923,9 @@ var init_web_backend = __esm({
220827
220923
  h.events.on("session:created", () => {
220828
220924
  this.pushSessionListToAll();
220829
220925
  });
220926
+ h.events.on("session:deleted", () => {
220927
+ this.pushSessionListToAll();
220928
+ });
220830
220929
  }
220831
220930
  setAppHostManager(manager) {
220832
220931
  this.appHostManager = manager;
@@ -220903,12 +221002,15 @@ var init_web_backend = __esm({
220903
221002
  });
220904
221003
  }
220905
221004
  this.broadcast({ type: "tool_end", name, result: resultStr, isError, images });
221005
+ this.broadcastContextWindow(false);
220906
221006
  }
220907
221007
  finishAssistantMessage() {
220908
221008
  this.broadcastSessionTime();
220909
221009
  this.stopSessionTimeBroadcast();
221010
+ const toolsForBroadcast2 = this.currentAssistant?.tools ?? [];
220910
221011
  this.currentAssistant = null;
220911
221012
  this.broadcast({ type: "assistant_end" });
221013
+ this.broadcastContextWindow(true, toolsForBroadcast2);
220912
221014
  const sm2 = this.harness.sessionManager;
220913
221015
  if (sm2) {
220914
221016
  const sessions2 = sm2.listSessions();
@@ -220934,6 +221036,7 @@ var init_web_backend = __esm({
220934
221036
  projectPath: s.projectPath || "",
220935
221037
  preview: s.preview || "",
220936
221038
  totalActiveMs: s.id === currentId2 ? sm2.getTotalActiveMs() : s.totalActiveMs ?? 0,
221039
+ contentHash: s.contentHash ?? "",
220937
221040
  pendingPermission: s.pendingPermission || void 0
220938
221041
  }))
220939
221042
  });
@@ -221013,9 +221116,11 @@ var init_web_backend = __esm({
221013
221116
  focusEditor() {
221014
221117
  }
221015
221118
  clearConversationView() {
221119
+ const toolsForBroadcast3 = this.currentAssistant?.tools ?? [];
221016
221120
  this.currentAssistant = null;
221017
221121
  this.pendingImages = [];
221018
221122
  this.broadcast({ type: "clear_conversation" });
221123
+ this.broadcastContextWindow(true, toolsForBroadcast3);
221019
221124
  }
221020
221125
  // ── UiBackend Processing ──
221021
221126
  setProcessing(processing) {
@@ -221060,6 +221165,7 @@ var init_web_backend = __esm({
221060
221165
  config: configData,
221061
221166
  messages
221062
221167
  });
221168
+ this.broadcastContextWindow(true);
221063
221169
  if (this.mcpManager) {
221064
221170
  this.pushMcpState();
221065
221171
  }
@@ -221252,6 +221358,10 @@ var init_web_backend = __esm({
221252
221358
  client.send({ type: "file_list_result", prefix: cmd.prefix, items });
221253
221359
  break;
221254
221360
  }
221361
+ case "artifact": {
221362
+ await this.handleArtifact(client, cmd);
221363
+ break;
221364
+ }
221255
221365
  case "mcp": {
221256
221366
  this.handleMcp(client, cmd);
221257
221367
  break;
@@ -221309,6 +221419,7 @@ var init_web_backend = __esm({
221309
221419
  projectPath: s.projectPath || "",
221310
221420
  preview: s.preview || "",
221311
221421
  totalActiveMs: s.id === currentId ? sessionManager.getTotalActiveMs() : s.totalActiveMs ?? 0,
221422
+ contentHash: s.contentHash ?? "",
221312
221423
  pendingPermission: s.pendingPermission || void 0
221313
221424
  }))
221314
221425
  });
@@ -221339,6 +221450,7 @@ var init_web_backend = __esm({
221339
221450
  projectPath: s.projectPath || "",
221340
221451
  preview: s.preview || "",
221341
221452
  totalActiveMs: s.id === currentId ? sm.getTotalActiveMs() : s.totalActiveMs ?? 0,
221453
+ contentHash: s.contentHash ?? "",
221342
221454
  pendingPermission: s.pendingPermission || void 0
221343
221455
  }))
221344
221456
  });
@@ -221446,7 +221558,8 @@ var init_web_backend = __esm({
221446
221558
  modelId: s.modelId,
221447
221559
  projectPath: s.projectPath || "",
221448
221560
  preview: s.preview || "",
221449
- totalActiveMs: s.totalActiveMs ?? 0
221561
+ totalActiveMs: s.totalActiveMs ?? 0,
221562
+ contentHash: s.contentHash ?? ""
221450
221563
  }))
221451
221564
  });
221452
221565
  }
@@ -221480,7 +221593,8 @@ var init_web_backend = __esm({
221480
221593
  modelId: s.modelId,
221481
221594
  projectPath: s.projectPath || "",
221482
221595
  preview: s.preview || "",
221483
- totalActiveMs: s.totalActiveMs ?? 0
221596
+ totalActiveMs: s.totalActiveMs ?? 0,
221597
+ contentHash: s.contentHash ?? ""
221484
221598
  }));
221485
221599
  client.send({ type: "sessions", data });
221486
221600
  break;
@@ -221506,7 +221620,8 @@ var init_web_backend = __esm({
221506
221620
  modelId: s.modelId,
221507
221621
  projectPath: s.projectPath || "",
221508
221622
  preview: s.preview || "",
221509
- totalActiveMs: s.totalActiveMs ?? 0
221623
+ totalActiveMs: s.totalActiveMs ?? 0,
221624
+ contentHash: s.contentHash ?? ""
221510
221625
  }))
221511
221626
  });
221512
221627
  break;
@@ -221601,6 +221716,7 @@ ${matchList}` });
221601
221716
  config: this.buildConfigData(),
221602
221717
  messages
221603
221718
  });
221719
+ this.broadcastContextWindow(true);
221604
221720
  this.pushSessionList(client);
221605
221721
  break;
221606
221722
  }
@@ -221609,15 +221725,21 @@ ${matchList}` });
221609
221725
  client.send({ type: "error", text: "Session ID required." });
221610
221726
  return;
221611
221727
  }
221728
+ const wasCurrent = sessionManager.getCurrentSessionId?.() === cmd.id;
221612
221729
  const result = sessionManager.deleteSession(cmd.id);
221613
221730
  if (!result.success) {
221614
221731
  client.send({ type: "error", text: `Failed to delete session: ${result.error}` });
221615
221732
  return;
221616
221733
  }
221734
+ if (wasCurrent) {
221735
+ client.send({ type: "clear_conversation" });
221736
+ }
221617
221737
  client.send({ type: "info", display: "toast", text: "Session deleted." });
221618
221738
  const sessions = sessionManager.listSessions();
221739
+ const currentId = sessionManager.getCurrentSessionId?.() ?? void 0;
221619
221740
  client.send({
221620
221741
  type: "sessions",
221742
+ currentSessionId: currentId,
221621
221743
  data: sessions.slice(0, 50).map((s) => ({
221622
221744
  id: s.id,
221623
221745
  title: s.title,
@@ -221628,7 +221750,8 @@ ${matchList}` });
221628
221750
  modelId: s.modelId,
221629
221751
  projectPath: s.projectPath || "",
221630
221752
  preview: s.preview || "",
221631
- totalActiveMs: s.totalActiveMs ?? 0
221753
+ totalActiveMs: s.totalActiveMs ?? 0,
221754
+ contentHash: s.contentHash ?? ""
221632
221755
  }))
221633
221756
  });
221634
221757
  break;
@@ -221716,6 +221839,44 @@ ${matchList}` });
221716
221839
  const vms = this.harness.sessionManager?.visionMessages ?? [];
221717
221840
  return rebuildDisplayMessages(messages, vms);
221718
221841
  }
221842
+ getSkillToolNames() {
221843
+ const names = /* @__PURE__ */ new Set();
221844
+ const sm = this.harness.skillManager;
221845
+ if (sm) {
221846
+ for (const name of sm.listAllSkillNames()) {
221847
+ names.add(name);
221848
+ }
221849
+ }
221850
+ return names;
221851
+ }
221852
+ broadcastContextWindow(bypassThrottle, toolsOverride) {
221853
+ const cm = this.harness.contextManager;
221854
+ const cw = cm.getContextWindow();
221855
+ if (cw <= 0) return;
221856
+ if (!bypassThrottle) {
221857
+ if (this.contextWindowThrottleTimer) {
221858
+ this.contextWindowThrottlePending = true;
221859
+ return;
221860
+ }
221861
+ this.contextWindowThrottleTimer = setTimeout(() => {
221862
+ this.contextWindowThrottleTimer = null;
221863
+ if (this.contextWindowThrottlePending) {
221864
+ this.contextWindowThrottlePending = false;
221865
+ this.broadcastContextWindow(true, toolsOverride);
221866
+ }
221867
+ }, 500);
221868
+ }
221869
+ const messages = this.harness.agent.state.messages;
221870
+ const tools = toolsOverride ?? this.currentAssistant?.tools ?? [];
221871
+ const breakdown = cm.getCategoryBreakdown(messages, tools, this.getSkillToolNames());
221872
+ this.broadcast({
221873
+ type: "context_window",
221874
+ total: breakdown.total,
221875
+ used: breakdown.used,
221876
+ free: breakdown.free,
221877
+ categories: breakdown.categories
221878
+ });
221879
+ }
221719
221880
  broadcast(event) {
221720
221881
  this.wsServer.broadcast(event);
221721
221882
  }
@@ -221791,6 +221952,224 @@ ${matchList}` });
221791
221952
  res.end("Not Found");
221792
221953
  }
221793
221954
  }
221955
+ async handleArtifact(client, cmd) {
221956
+ try {
221957
+ let styleConstraints = "";
221958
+ try {
221959
+ const skillPath = join28(this.config.projectPath, ".dscode", "html_output_skill");
221960
+ if (existsSync25(skillPath)) {
221961
+ styleConstraints = readFileSync23(skillPath, "utf-8");
221962
+ }
221963
+ } catch {
221964
+ }
221965
+ const designColors = `DESIGN SYSTEM COLORS (MUST use these exact hex values for EVERY color in your HTML):
221966
+ - Background: #f8f7f5
221967
+ - Surface: #f3f2ef
221968
+ - Border: #e6e4e0
221969
+ - Text: #2d2a26
221970
+ - Muted Text: #8a8580
221971
+ - Accent: #ca8a04 (warm amber/gold \u2014 use for highlights, progress bars, headings)
221972
+ - Success: #347539 text on #edf4ed background
221973
+ - Error: #9f2f2d text on #fdebec background
221974
+ - Warning: #956400 text on #fbf3db background
221975
+ - User Bubble: #ca8a04 background, #ffffff text`;
221976
+ const defaultStyleConstraints = `CRITICAL STYLE RULES:
221977
+ - Use the exact hex colors from DESIGN SYSTEM COLORS above \u2014 do NOT substitute with other greens, reds, or blues
221978
+ - Flat design: 1px solid var(--border) borders, no box-shadow, no gradients
221979
+ - Border-radius: 8px for cards/panels, 6px for buttons, 12px for large containers
221980
+ - Typography: system-ui, -apple-system, sans-serif for labels; monospace for data values
221981
+ - Emoji for visual markers, CSS conic-gradient or inline SVG for chart-like elements
221982
+ - Background MUST be #f8f7f5, never white (#fff) or transparent
221983
+ - Text MUST be #2d2a26, never pure black (#000) or cold gray
221984
+ - The accent color MUST be #ca8a04 (warm amber), never blue`;
221985
+ const systemPrompt = `You are an expert HTML dashboard designer. Generate a single, self-contained HTML file.
221986
+
221987
+ CRITICAL RULES:
221988
+ - Output ONLY valid HTML starting with <!DOCTYPE html>
221989
+ - All CSS MUST be inlined in <style> tags within <head>
221990
+ - NO external resources (fonts, images, scripts, CDN links)
221991
+ - Use system-ui, -apple-system, sans-serif for labels and monospace for data values
221992
+ - Use emoji icons for visual markers
221993
+ - Use CSS conic-gradient or inline SVG for chart-like elements
221994
+ - Make it visually rich and data-dense
221995
+ - Self-contained, single HTML document
221996
+
221997
+ ${designColors}
221998
+
221999
+ ${styleConstraints ? `ARTIFACT STYLE RULES (from .dscode/html_output_skill):
222000
+ ${styleConstraints}
222001
+ ` : defaultStyleConstraints}
222002
+
222003
+ Respond ONLY with the raw HTML starting with <!DOCTYPE html>. DO NOT wrap the output in markdown code fences (no \`\`\`html). DO NOT add any explanatory text before or after the HTML. Just output the HTML directly.`;
222004
+ let sessionSummary = "";
222005
+ if (cmd.context === "session_dashboard" || cmd.action === "generate") {
222006
+ sessionSummary = this.buildSessionSummary();
222007
+ }
222008
+ let userPrompt;
222009
+ if (cmd.action === "generate") {
222010
+ userPrompt = `Create a rich visual dashboard for this coding session. Use the data below to build a comprehensive, beautiful dashboard:
222011
+
222012
+ ${sessionSummary}
222013
+
222014
+ Make it visually stunning with emojis, progress bars, color-coded metrics, and CSS charts.`;
222015
+ } else {
222016
+ const existingHtml = this.lastArtifactHtml || "";
222017
+ userPrompt = `Here is the current dashboard HTML:
222018
+
222019
+ ${existingHtml.slice(0, 5e3)}
222020
+
222021
+ User instruction: ${cmd.instruction || "update the dashboard"}
222022
+
222023
+ Modify the HTML to fulfill the user's request. Output the complete modified HTML.`;
222024
+ }
222025
+ const model = resolveModel(this.config.provider, this.config.modelId);
222026
+ client.send({ type: "artifact_start" });
222027
+ let fullHtml = "";
222028
+ const stream = streamSimple3(model, {
222029
+ systemPrompt,
222030
+ messages: [{ role: "user", content: userPrompt, timestamp: Date.now() }]
222031
+ }, {
222032
+ apiKey: this.config.apiKey,
222033
+ maxTokens: this.config.maxTokens,
222034
+ timeoutMs: 12e4,
222035
+ maxRetries: 1
222036
+ });
222037
+ for await (const event of stream) {
222038
+ if (event.type === "text_delta") {
222039
+ fullHtml += event.delta;
222040
+ client.send({ type: "artifact_delta", delta: event.delta });
222041
+ } else if (event.type === "error") {
222042
+ client.send({ type: "error", text: `Artifact generation error: ${event.errorMessage || "Unknown error"}` });
222043
+ }
222044
+ }
222045
+ fullHtml = this.stripArtifactFences(fullHtml);
222046
+ this.lastArtifactHtml = fullHtml;
222047
+ client.send({ type: "artifact_end" });
222048
+ } catch (err) {
222049
+ client.send({ type: "artifact_end" });
222050
+ client.send({
222051
+ type: "error",
222052
+ text: `Artifact generation failed: ${err instanceof Error ? err.message : String(err)}`
222053
+ });
222054
+ }
222055
+ }
222056
+ stripArtifactFences(html) {
222057
+ let result = html.trim();
222058
+ const fencePattern = /^```(?:html)?\s*\n/;
222059
+ result = result.replace(fencePattern, "");
222060
+ const endFence = /\n```\s*$/;
222061
+ result = result.replace(endFence, "");
222062
+ if (!result.trimStart().startsWith("<")) {
222063
+ return html;
222064
+ }
222065
+ return result;
222066
+ }
222067
+ buildSessionSummary() {
222068
+ const sm = this.harness.sessionManager;
222069
+ const agent = this.harness.agent;
222070
+ const cm = this.harness.contextManager;
222071
+ const messages = agent.state.messages;
222072
+ const tools = this.currentAssistant?.tools ?? [];
222073
+ const breakdown = cm.getCategoryBreakdown(messages, tools, this.getSkillToolNames());
222074
+ const totalTokens = breakdown.total;
222075
+ const usedTokens = breakdown.used;
222076
+ const freeTokens = breakdown.free;
222077
+ const usagePercent = totalTokens > 0 ? Math.round(usedTokens / totalTokens * 100) : 0;
222078
+ const categories = breakdown.categories;
222079
+ const toolStats = /* @__PURE__ */ new Map();
222080
+ for (const msg of messages) {
222081
+ const m = msg;
222082
+ if (m.role === "assistant" && Array.isArray(m.content)) {
222083
+ for (const block of m.content) {
222084
+ if (block.type === "toolCall") {
222085
+ const name = block.name || "unknown";
222086
+ const existing = toolStats.get(name) || { calls: 0, errors: 0 };
222087
+ existing.calls++;
222088
+ toolStats.set(name, existing);
222089
+ }
222090
+ }
222091
+ }
222092
+ if (m.role === "toolResult" && m.isError) {
222093
+ const name = m.toolName || "unknown";
222094
+ const existing = toolStats.get(name);
222095
+ if (existing) {
222096
+ existing.errors++;
222097
+ } else {
222098
+ toolStats.set(name, { calls: 0, errors: 1 });
222099
+ }
222100
+ }
222101
+ }
222102
+ const toolEntries = Array.from(toolStats.entries()).map(([name, stats]) => ({
222103
+ name,
222104
+ callCount: stats.calls,
222105
+ errorCount: stats.errors,
222106
+ successCount: stats.calls - stats.errors,
222107
+ successRate: stats.calls > 0 ? Math.round((stats.calls - stats.errors) / stats.calls * 100) : 100
222108
+ })).sort((a, b) => b.callCount - a.callCount);
222109
+ const totalToolCalls = toolEntries.reduce((sum, t) => sum + t.callCount, 0);
222110
+ const totalToolErrors = toolEntries.reduce((sum, t) => sum + t.errorCount, 0);
222111
+ const sessionActiveMs = sm?.getTotalActiveMs?.() ?? 0;
222112
+ const turnCount = messages.filter((m) => m.role === "user").length;
222113
+ const avgTurnMs = turnCount > 0 ? Math.round(sessionActiveMs / turnCount) : 0;
222114
+ const pressureScore = totalTokens > 0 ? Math.round(usedTokens / totalTokens * 100) : 0;
222115
+ let pressureLabel = "low";
222116
+ if (pressureScore > 95) pressureLabel = "critical";
222117
+ else if (pressureScore > 80) pressureLabel = "high";
222118
+ else if (pressureScore > 50) pressureLabel = "moderate";
222119
+ const topTools = toolEntries.slice(0, 3).map((t) => ({
222120
+ name: t.name,
222121
+ successRate: t.successRate,
222122
+ warning: t.errorCount > 0 && t.errorCount / t.callCount > 0.2 ? "\u26A0\uFE0F" : ""
222123
+ }));
222124
+ function formatMs(ms) {
222125
+ if (ms < 1e3) return `${ms}ms`;
222126
+ const sec = Math.floor(ms / 1e3);
222127
+ if (sec < 60) return `${sec}s`;
222128
+ const min = Math.floor(sec / 60);
222129
+ const remainSec = sec % 60;
222130
+ if (min < 60) return `${min}m ${remainSec}s`;
222131
+ const hrs = Math.floor(min / 60);
222132
+ const remainMin = min % 60;
222133
+ return `${hrs}h ${remainMin}m`;
222134
+ }
222135
+ return JSON.stringify({
222136
+ tokenUsage: {
222137
+ total: totalTokens,
222138
+ used: usedTokens,
222139
+ free: freeTokens,
222140
+ usagePercent,
222141
+ categories: {
222142
+ system: categories.system ?? null,
222143
+ rules: categories.rules ?? null,
222144
+ user: categories.user ?? null,
222145
+ thinking: categories.thinking ?? null,
222146
+ readwrite: categories.readwrite ?? null,
222147
+ edit: categories.edit ?? null,
222148
+ shell: categories.shell ?? null,
222149
+ skill: categories.skill ?? null,
222150
+ mcp: categories.mcp ?? null,
222151
+ other: categories.other ?? null
222152
+ }
222153
+ },
222154
+ toolStatistics: {
222155
+ tools: toolEntries,
222156
+ totalToolsCalled: totalToolCalls,
222157
+ totalErrors: totalToolErrors
222158
+ },
222159
+ timing: {
222160
+ sessionActiveMs,
222161
+ sessionActiveFormatted: formatMs(sessionActiveMs),
222162
+ turnCount,
222163
+ avgTurnMs,
222164
+ avgTurnFormatted: formatMs(avgTurnMs)
222165
+ },
222166
+ contextHealth: {
222167
+ pressureScore,
222168
+ pressureLabel
222169
+ },
222170
+ topTools
222171
+ }, null, 2);
222172
+ }
221794
222173
  };
221795
222174
  }
221796
222175
  });
@@ -222033,6 +222412,7 @@ var SessionStore = class {
222033
222412
 
222034
222413
  // src/session/manager.ts
222035
222414
  import { existsSync as existsSync5, readFileSync as readFileSync4, readdirSync as readdirSync2 } from "node:fs";
222415
+ import { createHash as createHash3 } from "node:crypto";
222036
222416
  import { join as join5 } from "node:path";
222037
222417
  function ulid() {
222038
222418
  const t = Date.now().toString(36).padStart(10, "0");
@@ -222126,6 +222506,7 @@ var SessionManager = class {
222126
222506
  preview: "",
222127
222507
  hasImages: false,
222128
222508
  imageCount: 0,
222509
+ contentHash: "",
222129
222510
  totalActiveMs: 0
222130
222511
  };
222131
222512
  this.accumulatedMs = 0;
@@ -222182,6 +222563,8 @@ var SessionManager = class {
222182
222563
  if (!this.current.preview) {
222183
222564
  this.current.preview = extractFirstUserMessage(messages);
222184
222565
  }
222566
+ const contentHash2 = createHash3("sha256").update(messages.map((m) => `${m.role}:${String(m.content ?? "").slice(0, 200)}`).join("|")).digest("hex").slice(0, 12);
222567
+ this.current.contentHash = contentHash2;
222185
222568
  this.current.projectPath = this.projectPath;
222186
222569
  let totalImages = 0;
222187
222570
  let hasImages = false;
@@ -222253,6 +222636,7 @@ var SessionManager = class {
222253
222636
  deleteSession(id) {
222254
222637
  try {
222255
222638
  this.store.delete(id);
222639
+ if (id === this.current?.id) this.current = null;
222256
222640
  this.events?.emit({ type: "session:deleted", id });
222257
222641
  return { success: true };
222258
222642
  } catch (err) {
@@ -222402,6 +222786,99 @@ var ContextManager = class {
222402
222786
  const reservedTools = 3e3;
222403
222787
  return this.contextWindow - this.maxTokens - reservedSystem - reservedTools;
222404
222788
  }
222789
+ getContextWindow() {
222790
+ return this.contextWindow;
222791
+ }
222792
+ classifyToolCategory(toolName, skillNames) {
222793
+ if (toolName.startsWith("mcp__")) return "mcp";
222794
+ if (skillNames?.has(toolName)) return "skill";
222795
+ if (toolName === "read_file" || toolName === "list_files" || toolName === "grep" || toolName === "glob" || toolName === "write_file" || toolName === "overwrite_file")
222796
+ return "readwrite";
222797
+ if (toolName === "edit" || toolName === "edit_undo") return "edit";
222798
+ if (toolName === "bash") return "shell";
222799
+ return "other";
222800
+ }
222801
+ getCategoryBreakdown(messages, tools, skillNames) {
222802
+ const categories = {
222803
+ system: 0,
222804
+ rules: 0,
222805
+ user: 0,
222806
+ thinking: 0,
222807
+ readwrite: 0,
222808
+ edit: 0,
222809
+ shell: 0,
222810
+ skill: 0,
222811
+ mcp: 0,
222812
+ other: 0
222813
+ };
222814
+ let systemMsgIndex = 0;
222815
+ for (const msg of messages) {
222816
+ const m = msg;
222817
+ const role = m?.role;
222818
+ const content = m?.content;
222819
+ if (role === "system") {
222820
+ systemMsgIndex++;
222821
+ const target = systemMsgIndex === 1 ? "system" : "rules";
222822
+ if (typeof content === "string") {
222823
+ categories[target] += estimateTokens(content);
222824
+ categories[target] += 4;
222825
+ } else if (Array.isArray(content)) {
222826
+ for (const block of content) {
222827
+ if (block?.type === "text" && typeof block.text === "string") {
222828
+ categories[target] += estimateTokens(block.text);
222829
+ }
222830
+ }
222831
+ categories[target] += 4;
222832
+ }
222833
+ } else if (role === "user") {
222834
+ if (typeof content === "string") {
222835
+ categories.user += estimateTokens(content);
222836
+ } else if (Array.isArray(content)) {
222837
+ for (const block of content) {
222838
+ if (block?.type === "text" && typeof block.text === "string") {
222839
+ categories.user += estimateTokens(block.text);
222840
+ }
222841
+ }
222842
+ }
222843
+ categories.user += 4;
222844
+ } else if (role === "assistant") {
222845
+ if (typeof content === "string") {
222846
+ categories.thinking += estimateTokens(content);
222847
+ } else if (Array.isArray(content)) {
222848
+ for (const block of content) {
222849
+ if (block?.type === "text" && typeof block.text === "string") {
222850
+ categories.thinking += estimateTokens(block.text);
222851
+ } else if (block?.type === "thinking" && typeof block.thinking === "string") {
222852
+ categories.thinking += estimateTokens(block.thinking);
222853
+ }
222854
+ }
222855
+ }
222856
+ categories.thinking += 4;
222857
+ } else if (role === "toolResult") {
222858
+ const toolName = m?.toolName ?? "";
222859
+ const cat = this.classifyToolCategory(toolName, skillNames);
222860
+ if (Array.isArray(content)) {
222861
+ for (const block of content) {
222862
+ if (block?.type === "text" && typeof block.text === "string") {
222863
+ categories[cat] += estimateTokens(block.text);
222864
+ }
222865
+ }
222866
+ } else if (typeof content === "string") {
222867
+ categories[cat] += estimateTokens(content);
222868
+ }
222869
+ categories[cat] += 20;
222870
+ }
222871
+ }
222872
+ for (const tool of tools) {
222873
+ const cat = this.classifyToolCategory(tool.name, skillNames);
222874
+ const resultTokens = typeof tool.result === "string" ? estimateTokens(tool.result) : 0;
222875
+ categories[cat] += resultTokens + 20;
222876
+ }
222877
+ const used = Object.values(categories).reduce((a, b) => a + b, 0);
222878
+ const total = this.contextWindow;
222879
+ const free = Math.max(0, total - used);
222880
+ return { total, used, free, categories };
222881
+ }
222405
222882
  getEstimatedTokens(messages) {
222406
222883
  return estimateMessagesTokens(messages);
222407
222884
  }
@@ -222409,14 +222886,14 @@ var ContextManager = class {
222409
222886
 
222410
222887
  // src/memory/store.ts
222411
222888
  import { existsSync as existsSync6, mkdirSync as mkdirSync5, readFileSync as readFileSync5, writeFileSync as writeFileSync5 } from "node:fs";
222412
- import { createHash as createHash3 } from "node:crypto";
222889
+ import { createHash as createHash4 } from "node:crypto";
222413
222890
  import { join as join6 } from "node:path";
222414
222891
  var MemoryStore = class {
222415
222892
  dataDir;
222416
222893
  projectHash;
222417
222894
  constructor(dataDir, projectPath) {
222418
222895
  this.dataDir = join6(dataDir, "memory");
222419
- this.projectHash = createHash3("sha256").update(projectPath).digest("hex").slice(0, 12);
222896
+ this.projectHash = createHash4("sha256").update(projectPath).digest("hex").slice(0, 12);
222420
222897
  if (!existsSync6(this.dataDir)) {
222421
222898
  mkdirSync5(this.dataDir, { recursive: true });
222422
222899
  }
@@ -222531,16 +223008,16 @@ import { join as join9, resolve as resolve2, dirname as dirname2 } from "node:pa
222531
223008
  import { Type as Type2 } from "@mariozechner/pi-ai";
222532
223009
 
222533
223010
  // src/drivers/edit/hash.ts
222534
- import { createHash as createHash4 } from "node:crypto";
223011
+ import { createHash as createHash5 } from "node:crypto";
222535
223012
  import { Type } from "@mariozechner/pi-ai";
222536
223013
  function computeLineHash(line) {
222537
- return createHash4("md5").update(line.trim()).digest("hex").slice(0, 6);
223014
+ return createHash5("md5").update(line.trim()).digest("hex").slice(0, 6);
222538
223015
  }
222539
223016
  function computeResolutionHash(line) {
222540
- return createHash4("md5").update(line.trim()).digest("hex").slice(0, 8);
223017
+ return createHash5("md5").update(line.trim()).digest("hex").slice(0, 8);
222541
223018
  }
222542
223019
  function computeFileVersion(content) {
222543
- return "fv_" + createHash4("sha256").update(content).digest("hex").slice(0, 8);
223020
+ return "fv_" + createHash5("sha256").update(content).digest("hex").slice(0, 8);
222544
223021
  }
222545
223022
  function hashLines(lines) {
222546
223023
  const resolutionMap = /* @__PURE__ */ new Map();
@@ -222593,7 +223070,7 @@ function computeContextHash(lines, lineIdx) {
222593
223070
  const prev = findNonEmptyBefore(lines, lineIdx);
222594
223071
  const curr = lines[lineIdx].trim();
222595
223072
  const next = findNonEmptyAfter(lines, lineIdx);
222596
- return createHash4("md5").update(`${prev}
223073
+ return createHash5("md5").update(`${prev}
222597
223074
  ${curr}
222598
223075
  ${next}`).digest("hex").slice(0, 8);
222599
223076
  }
@@ -223192,7 +223669,7 @@ function generateLocalDiff(oldLines, newLines, ops, ctx) {
223192
223669
  // src/checkpoint/index.ts
223193
223670
  import { homedir as homedir4 } from "node:os";
223194
223671
  import { join as join8 } from "node:path";
223195
- import { createHash as createHash5 } from "node:crypto";
223672
+ import { createHash as createHash6 } from "node:crypto";
223196
223673
 
223197
223674
  // src/checkpoint/checkpoint-manager.ts
223198
223675
  import { existsSync as existsSync7, readFileSync as readFileSync6, writeFileSync as writeFileSync6, mkdirSync as mkdirSync6, unlinkSync as unlinkSync2 } from "node:fs";
@@ -223498,7 +223975,7 @@ var _snapshotStore = null;
223498
223975
  var _checkpointManager = null;
223499
223976
  var _fileWriteTracker = null;
223500
223977
  function initCheckpointSystem(projectPath, sessionId) {
223501
- const projectHash = createHash5("sha256").update(projectPath).digest("hex").slice(0, 12);
223978
+ const projectHash = createHash6("sha256").update(projectPath).digest("hex").slice(0, 12);
223502
223979
  const baseDir = join8(homedir4(), ".dscode", "checkpoints", "per-project", projectHash, sessionId);
223503
223980
  _snapshotStore = new SnapshotStore();
223504
223981
  const store = new FileSystemCheckpointStore(baseDir);
@@ -224101,7 +224578,7 @@ import { resolve as resolve4 } from "node:path";
224101
224578
  import { Type as Type5 } from "@mariozechner/pi-ai";
224102
224579
 
224103
224580
  // src/drivers/edit/recovery.ts
224104
- import { createHash as createHash6 } from "node:crypto";
224581
+ import { createHash as createHash7 } from "node:crypto";
224105
224582
  function recoverBySnapshot(snapshotLines, currentLines, operations, snapshotCtx) {
224106
224583
  let expectedLines;
224107
224584
  try {
@@ -224281,7 +224758,7 @@ function computeContextHash2(lines, lineIdx) {
224281
224758
  const prev = findNonEmptyBefore2(lines, lineIdx);
224282
224759
  const curr = lines[lineIdx].trim();
224283
224760
  const next = findNonEmptyAfter2(lines, lineIdx);
224284
- return createHash6("md5").update(`${prev}
224761
+ return createHash7("md5").update(`${prev}
224285
224762
  ${curr}
224286
224763
  ${next}`).digest("hex").slice(0, 8);
224287
224764
  }
@@ -228200,6 +228677,7 @@ var ConversationView = class {
228200
228677
  this.thinkingBuffer = "";
228201
228678
  this.toolEntries = [];
228202
228679
  this.renderedToolCount = 0;
228680
+ this.tui.requestRender(true);
228203
228681
  }
228204
228682
  thinkingDelta(delta) {
228205
228683
  this.thinkingBuffer += delta;
@@ -228551,7 +229029,7 @@ var ConversationView = class {
228551
229029
  this.box.addChild(permText);
228552
229030
  this.liveComponents.push(permText);
228553
229031
  }
228554
- this.tui.requestRender(true);
229032
+ this.tui.requestRender(false);
228555
229033
  }
228556
229034
  };
228557
229035
 
@@ -228749,6 +229227,23 @@ var HybridAutocompleteProvider = class {
228749
229227
  return true;
228750
229228
  }
228751
229229
  };
229230
+ function formatTokens(n) {
229231
+ if (n >= 1e6) return `${(n / 1e6).toFixed(1)}M`;
229232
+ if (n >= 1e3) return `${(n / 1e3).toFixed(1)}k`;
229233
+ return String(n);
229234
+ }
229235
+ function formatContextPercent(estimated, contextWindow) {
229236
+ if (contextWindow <= 0 || !isFinite(estimated) || Number.isNaN(estimated)) return null;
229237
+ const pct = Math.round(estimated / contextWindow * 100);
229238
+ const text = `\u2593\u2593 ${pct}%`;
229239
+ if (pct > 95) return c.red(text);
229240
+ if (pct > 80) return c.yellow(text);
229241
+ return c.dim(text);
229242
+ }
229243
+ function formatCost(total) {
229244
+ if (total >= 0.01) return `$${total.toFixed(4)}`;
229245
+ return `\xA2${(total * 100).toFixed(2)}`;
229246
+ }
228752
229247
  var TuiApp = class {
228753
229248
  deps;
228754
229249
  terminal;
@@ -229418,13 +229913,27 @@ var TuiApp = class {
229418
229913
  this.markActivity();
229419
229914
  this.conversation.toolEnd(name, result, isError);
229420
229915
  }
229421
- finishAssistantMessage() {
229916
+ finishAssistantMessage(usage) {
229422
229917
  this.finalizeIdleSegment();
229423
229918
  this.conversation.finishAssistantMessage();
229919
+ const parts = [];
229424
229920
  if (this.totalWaitMs >= 1e3) {
229425
- this.conversation.addInfo(
229426
- c.dim(`\u23F1 total wait: ${this.formatElapsed(this.totalWaitMs)} (${this.waitSegments.length} segment${this.waitSegments.length > 1 ? "s" : ""})`)
229427
- );
229921
+ parts.push(`\u23F1 ${this.formatElapsed(this.totalWaitMs)}`);
229922
+ }
229923
+ if (usage && usage.input > 0 && usage.output > 0) {
229924
+ parts.push(`\u{1F4CA} ${formatTokens(usage.input)}\u2193 ${formatTokens(usage.output)}\u2191`);
229925
+ }
229926
+ {
229927
+ const estimatedTokens = this.deps.contextManager.getEstimatedTokens(this.deps.agent.state.messages);
229928
+ const contextWindow = this.deps.contextManager.getContextWindow();
229929
+ const ctxStr = formatContextPercent(estimatedTokens, contextWindow);
229930
+ if (ctxStr) parts.push(ctxStr);
229931
+ }
229932
+ if (usage && usage.cost.total > 0) {
229933
+ parts.push(`\u{1F4B0} ${formatCost(usage.cost.total)}`);
229934
+ }
229935
+ if (parts.length > 0) {
229936
+ this.conversation.addInfo(c.dim(parts.join(" \xB7 ")));
229428
229937
  }
229429
229938
  }
229430
229939
  addInfo(text, _display) {
@@ -229708,8 +230217,8 @@ var TuiBackend = class {
229708
230217
  deps.events.on("turn:streaming:start", () => {
229709
230218
  this.tui.startAssistantMessage();
229710
230219
  });
229711
- deps.events.on("turn:end", () => {
229712
- this.tui.finishAssistantMessage();
230220
+ deps.events.on("turn:end", (e) => {
230221
+ this.tui.finishAssistantMessage(e.usage);
229713
230222
  });
229714
230223
  deps.events.on("message:user", (e) => {
229715
230224
  this.tui.addUserMessage(e.text);
@@ -230957,7 +231466,8 @@ You have a \`skill\` tool available. When you decide to use a skill from the lis
230957
231466
  if (event.type === "turn_end") {
230958
231467
  this.sessionManager.trySaveSession(this.agent);
230959
231468
  const turnEndMsg = event.message;
230960
- this.events.emit({ type: "turn:end", stopReason: turnEndMsg?.stopReason, usage: turnEndMsg?.usage });
231469
+ const rawUsage = turnEndMsg?.usage;
231470
+ this.events.emit({ type: "turn:end", stopReason: turnEndMsg?.stopReason, usage: rawUsage ? { input: rawUsage.input, output: rawUsage.output, cacheRead: rawUsage.cacheRead, cacheWrite: rawUsage.cacheWrite, total: rawUsage.totalTokens, cost: { total: rawUsage.cost.total } } : void 0 });
230961
231471
  const msg = turnEndMsg;
230962
231472
  if (msg?.stopReason === "length") {
230963
231473
  this.events.emit({ type: "ui:info", text: "Output truncated (hit max_tokens). Continue from where you left off." });