scream-code 0.16.3 → 0.16.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.
@@ -4,8 +4,8 @@ import { dirname as __cjsShimDirname } from 'node:path';
4
4
  const __filename = __cjsShimFileURLToPath(import.meta.url);
5
5
  const __dirname = __cjsShimDirname(__filename);
6
6
  import "./suppress-sqlite-warning-C2VB0doZ.mjs";
7
- import { $n as readSessionIndex, $t as SubagentSlots, A as langFromPath, An as startManualEmbeddingDownload, At as toggleEmptySessionHint, B as charsForTokenBudget, Bn as getLogDir, Bt as OSC11_QUERY, C as getDaemonInstructions, Cn as buildSkillSlashCommands, D as renderDiffLines, Dn as setExperimentalFlags, Dt as createMarkdownTheme, E as ToolCallComponent, En as isExperimentalFlagEnabled, Et as createEditorTheme, F as replaceTabs, Fn as loadTuiConfig, Ft as parseOsc11BackgroundTheme, G as WelcomeComponent, Gn as PRODUCT_NAME, Gt as TERMINAL_FOCUS_IN, H as getSharedSpeedTracker, Hn as CLI_COMMAND_NAME, Ht as OSC11_RESPONSE_PREFIX, I as sanitizeShellOutput, In as saveTuiConfig, It as DISABLE_TERMINAL_FOCUS_REPORTING, J as resetBreathingClock, Jn as saveCatalogCache, Jt as TERMINAL_THEME_LIGHT, K as BREATHE_CYCLE_MS, Kn as DEFAULT_CATALOG_URL, Kt as TERMINAL_FOCUS_OUT, L as SkillActivationComponent, Ln as detectShellEnvironment, Lt as DISABLE_TERMINAL_THEME_REPORTING, M as ThinkingComponent, Mn as PULSE_WAVE_FRAMES, Mt as contrastTextHex, N as NoticeMessageComponent, Nn as TuiConfigParseError, Nt as getColorPalette, O as renderDiffLinesClustered, On as getKnowledgeStore, Ot as isEmptySessionHintDismissed, P as StatusMessageComponent, Pn as TuiLikePreferencesSchema, Pt as detectTerminalTheme, Qn as appendSessionIndexEntry, Qt as lerpGradient, R as AssistantMessageComponent, Rn as getDataDir, Rt as ENABLE_TERMINAL_FOCUS_REPORTING, Sn as getNoActiveSessionMessage, St as clearInfoPanelState, T as UserMessageComponent, Tn as sortSlashCommands, Tt as resolveThemeSync, U as BackgroundAgentStatusComponent, Un as CLI_UI_MODE, Ut as OSC11_RESPONSE_PREFIX_NO_ESC, V as estimateTokens, Vn as detectInstallSource, Vt as OSC11_RESPONSE, W as AgentGroupComponent, Wn as CLI_USER_AGENT_PRODUCT, Wt as QUERY_TERMINAL_THEME, X as clearGoalState, Xn as encodeWorkDirKey, Xt as isStreaming, Y as CompactionComponent, Yn as ScreamHarness, Yt as isBusy, Z as refineGoal, Zn as normalizeWorkDir, Zt as FooterComponent, _ as selectUpdateTarget, _n as SESSION_TIPS, an as STATUS_BULLET, ar as log, at as changeThinkingLevel, b as appendJsonlLine, bn as getCtrlDHint, bt as refreshProviderBalance, c as hasDispose, cn as formatErrorMessage, cr as isOrphanedToolCallError, d as MoonLoader, dn as serializeToolResultOutput, dt as handleFusionPlanCommand, er as removeSessionIndexEntry, f as formatMemoryMemoForInjection, fn as stringValue, gn as MAIN_AGENT_ID, hn as EXIT_CONFIRM_WINDOW_MS, i as buildRoleAdditionalText, in as printableChar, ir as flushDiagnosticLogs, it as toTerminalHyperlink, j as CachedContainer, jn as PIXEL_PULSE_FRAMES, jt as createThemeStyles, k as highlightLines, kn as isEmbeddingModelCached, kt as isTurnElapsedEnabled, l as isExpandable, ln as isTodoItemShape, lr as ErrorCodes, mn as EMPTY_SESSION_HINT_URL, n as clearEvalPanelState, nr as resolveScreamHome, on as appendStreamingArgsPreview, or as resolveGlobalLogPath, ot as getModelCycleLevel, pn as truncateErrorMessage, pt as handlePlanCommand, q as getBreathingFrame, qn as fetchCatalog, qt as TERMINAL_THEME_DARK, r as openUrl, rr as MemoryMemoStore, rt as isInternalMessage, s as disposeChildren, sn as argsRecord, sr as isScreamError, t as dispatchInput, u as isPlanExpandable, un as parseStreamingArgs, ur as SCREAM_ERROR_INFO, v as refreshUpdateCache, vn as TIP_ROTATION_INTERVAL_MS, wn as BUILTIN_SLASH_COMMANDS, x as readJsonlFile, xn as getLlmNotSetMessage, xt as supportsBalance, y as readUpdateCache, yn as getCtrlCHint, z as ActivityGroupComponent, zn as getInputHistoryFile, zt as ENABLE_TERMINAL_THEME_REPORTING } from "./dispatch-ChDxIz0h.mjs";
8
- import { a as setLocale, i as getLocale, o as t } from "./text-input-dialog-GajpxjyL.mjs";
7
+ import { $n as readSessionIndex, $t as SubagentSlots, A as langFromPath, An as startManualEmbeddingDownload, At as toggleEmptySessionHint, B as charsForTokenBudget, Bn as getLogDir, Bt as OSC11_QUERY, C as getDaemonInstructions, Cn as buildSkillSlashCommands, D as renderDiffLines, Dn as setExperimentalFlags, Dt as createMarkdownTheme, E as ToolCallComponent, En as isExperimentalFlagEnabled, Et as createEditorTheme, F as replaceTabs, Fn as loadTuiConfig, Ft as parseOsc11BackgroundTheme, G as WelcomeComponent, Gn as PRODUCT_NAME, Gt as TERMINAL_FOCUS_IN, H as getSharedSpeedTracker, Hn as CLI_COMMAND_NAME, Ht as OSC11_RESPONSE_PREFIX, I as sanitizeShellOutput, In as saveTuiConfig, It as DISABLE_TERMINAL_FOCUS_REPORTING, J as resetBreathingClock, Jn as saveCatalogCache, Jt as TERMINAL_THEME_LIGHT, K as BREATHE_CYCLE_MS, Kn as DEFAULT_CATALOG_URL, Kt as TERMINAL_FOCUS_OUT, L as SkillActivationComponent, Ln as detectShellEnvironment, Lt as DISABLE_TERMINAL_THEME_REPORTING, M as ThinkingComponent, Mn as PULSE_WAVE_FRAMES, Mt as contrastTextHex, N as NoticeMessageComponent, Nn as TuiConfigParseError, Nt as getColorPalette, O as renderDiffLinesClustered, On as getKnowledgeStore, Ot as isEmptySessionHintDismissed, P as StatusMessageComponent, Pn as TuiLikePreferencesSchema, Pt as detectTerminalTheme, Qn as appendSessionIndexEntry, Qt as lerpGradient, R as AssistantMessageComponent, Rn as getDataDir, Rt as ENABLE_TERMINAL_FOCUS_REPORTING, Sn as getNoActiveSessionMessage, St as clearInfoPanelState, T as UserMessageComponent, Tn as sortSlashCommands, Tt as resolveThemeSync, U as BackgroundAgentStatusComponent, Un as CLI_UI_MODE, Ut as OSC11_RESPONSE_PREFIX_NO_ESC, V as estimateTokens, Vn as detectInstallSource, Vt as OSC11_RESPONSE, W as AgentGroupComponent, Wn as CLI_USER_AGENT_PRODUCT, Wt as QUERY_TERMINAL_THEME, X as clearGoalState, Xn as encodeWorkDirKey, Xt as isStreaming, Y as CompactionComponent, Yn as ScreamHarness, Yt as isBusy, Z as refineGoal, Zn as normalizeWorkDir, Zt as FooterComponent, _ as selectUpdateTarget, _n as SESSION_TIPS, an as STATUS_BULLET, ar as log, at as changeThinkingLevel, b as appendJsonlLine, bn as getCtrlDHint, bt as refreshProviderBalance, c as hasDispose, cn as formatErrorMessage, cr as isOrphanedToolCallError, d as MoonLoader, dn as serializeToolResultOutput, dt as handleFusionPlanCommand, er as removeSessionIndexEntry, f as formatMemoryMemoForInjection, fn as stringValue, gn as MAIN_AGENT_ID, hn as EXIT_CONFIRM_WINDOW_MS, i as buildRoleAdditionalText, in as printableChar, ir as flushDiagnosticLogs, it as toTerminalHyperlink, j as CachedContainer, jn as PIXEL_PULSE_FRAMES, jt as createThemeStyles, k as highlightLines, kn as isEmbeddingModelCached, kt as isTurnElapsedEnabled, l as isExpandable, ln as isTodoItemShape, lr as ErrorCodes, mn as EMPTY_SESSION_HINT_URL, n as clearEvalPanelState, nr as resolveScreamHome, on as appendStreamingArgsPreview, or as resolveGlobalLogPath, ot as getModelCycleLevel, pn as truncateErrorMessage, pt as handlePlanCommand, q as getBreathingFrame, qn as fetchCatalog, qt as TERMINAL_THEME_DARK, r as openUrl, rr as MemoryMemoStore, rt as isInternalMessage, s as disposeChildren, sn as argsRecord, sr as isScreamError, t as dispatchInput, u as isPlanExpandable, un as parseStreamingArgs, ur as SCREAM_ERROR_INFO, v as refreshUpdateCache, vn as TIP_ROTATION_INTERVAL_MS, wn as BUILTIN_SLASH_COMMANDS, x as readJsonlFile, xn as getLlmNotSetMessage, xt as supportsBalance, y as readUpdateCache, yn as getCtrlCHint, z as ActivityGroupComponent, zn as getInputHistoryFile, zt as ENABLE_TERMINAL_THEME_REPORTING } from "./dispatch-La1sAZPF.mjs";
8
+ import { a as setLocale, i as getLocale, o as t } from "./text-input-dialog--J3ngWFv.mjs";
9
9
  import { createRequire } from "node:module";
10
10
  import { randomBytes, randomUUID, scryptSync, timingSafeEqual } from "node:crypto";
11
11
  import { access, appendFile, chmod, mkdir, mkdtemp, readFile, readdir, realpath, rename, rm, stat, unlink, writeFile } from "node:fs/promises";
@@ -32,7 +32,7 @@ function optionalBuildString(value) {
32
32
  return typeof value === "string" && value.length > 0 ? value : void 0;
33
33
  }
34
34
  const SCREAM_BUILD_INFO = {
35
- version: optionalBuildString("0.16.3"),
35
+ version: optionalBuildString("0.16.4"),
36
36
  channel: optionalBuildString(""),
37
37
  commit: optionalBuildString(""),
38
38
  buildTarget: optionalBuildString("darwin-arm64")
@@ -13882,7 +13882,7 @@ var ScreamTUI = class {
13882
13882
  const card = new OnboardingCardComponent({
13883
13883
  colors: this.state.theme.colors,
13884
13884
  onConfigure: () => {
13885
- import("./dispatch-BGLqpKHG.mjs").then(({ handleConnectCommand }) => handleConnectCommand(this, "")).finally(() => {
13885
+ import("./dispatch-C7ujqj4p.mjs").then(({ handleConnectCommand }) => handleConnectCommand(this, "")).finally(() => {
13886
13886
  this.restoreEditor();
13887
13887
  onDone?.();
13888
13888
  });
@@ -18343,6 +18343,143 @@ var SessionManager = class {
18343
18343
  }
18344
18344
  };
18345
18345
  /**
18346
+ * Session-backed data routes: snapshot, message history / thinking entries,
18347
+ * git status and single-file diff, plus the read-only file gate. Returns true
18348
+ * when the request was handled (including the not-found arms).
18349
+ */
18350
+ async function handleSessionDataRoutes(req, res, url, method, resolveSession, workDir, fileGate) {
18351
+ const snapshotMatch = new RegExp(`^${API_PREFIX}/sessions/([^/]+)/snapshot(\\?|$)`).exec(url);
18352
+ if (snapshotMatch && method === "GET") {
18353
+ let sessionId;
18354
+ try {
18355
+ sessionId = decodeURIComponent(snapshotMatch[1]);
18356
+ } catch {
18357
+ res.writeHead(404, { "Content-Type": "application/json" });
18358
+ res.end(JSON.stringify({
18359
+ code: 404,
18360
+ message: "Session not found"
18361
+ }));
18362
+ return true;
18363
+ }
18364
+ const ws = resolveSession(sessionId);
18365
+ if (!ws) {
18366
+ res.writeHead(404, { "Content-Type": "application/json" });
18367
+ res.end(JSON.stringify({
18368
+ code: 404,
18369
+ message: "Session not found"
18370
+ }));
18371
+ return true;
18372
+ }
18373
+ try {
18374
+ await ws.ready;
18375
+ const query = new URLSearchParams(url.split("?")[1] ?? "");
18376
+ const tail = Number(query.get("tail") ?? "");
18377
+ sendJson(res, 200, Number.isFinite(tail) && tail > 0 ? ws.getSnapshot({ tail }) : ws.getSnapshot());
18378
+ } catch (error) {
18379
+ sendHttpError(res, error);
18380
+ }
18381
+ return true;
18382
+ }
18383
+ const olderMatch = new RegExp(`^${API_PREFIX}/sessions/([^/?]+)/messages(\\?|$)`).exec(url);
18384
+ if (olderMatch && method === "GET") {
18385
+ let sessionId;
18386
+ try {
18387
+ sessionId = decodeURIComponent(olderMatch[1]);
18388
+ } catch {
18389
+ res.writeHead(404, { "Content-Type": "application/json" });
18390
+ res.end(JSON.stringify({
18391
+ code: 404,
18392
+ message: "Session not found"
18393
+ }));
18394
+ return true;
18395
+ }
18396
+ const ws = resolveSession(sessionId);
18397
+ if (!ws) {
18398
+ res.writeHead(404, { "Content-Type": "application/json" });
18399
+ res.end(JSON.stringify({
18400
+ code: 404,
18401
+ message: "Session not found"
18402
+ }));
18403
+ return true;
18404
+ }
18405
+ try {
18406
+ await ws.ready;
18407
+ const query = new URLSearchParams(url.split("?")[1] ?? "");
18408
+ const seq = Number(query.get("seq") ?? "0");
18409
+ const tool = query.get("tool") ?? "";
18410
+ if (Number.isFinite(seq) && seq > 0 && tool) {
18411
+ sendJson(res, 200, { output: ws.getThinkingEntry(seq, tool) });
18412
+ return true;
18413
+ }
18414
+ const before = Number(query.get("before") ?? "0");
18415
+ const tail = Number(query.get("tail") ?? "50");
18416
+ sendJson(res, 200, Number.isFinite(before) && before > 0 ? ws.getMessagesOlder(before, Number.isFinite(tail) && tail > 0 ? tail : 50) : {
18417
+ messages: [],
18418
+ hasMore: false
18419
+ });
18420
+ } catch (error) {
18421
+ sendHttpError(res, error);
18422
+ }
18423
+ return true;
18424
+ }
18425
+ if (url === `${API_PREFIX}/git/status` && method === "GET") {
18426
+ const gs = await getGitStatus(workDir);
18427
+ res.writeHead(200, { "Content-Type": "application/json" });
18428
+ res.end(JSON.stringify(gs));
18429
+ return true;
18430
+ }
18431
+ if (url.startsWith(`${API_PREFIX}/git/diff?`) && method === "GET") {
18432
+ const relPath = new URLSearchParams(url.split("?")[1] ?? "").get("path") ?? "";
18433
+ try {
18434
+ const result = await getGitFileDiff(workDir, relPath);
18435
+ res.writeHead(200, { "Content-Type": "application/json" });
18436
+ res.end(JSON.stringify({
18437
+ path: relPath,
18438
+ patch: result?.patch ?? ""
18439
+ }));
18440
+ } catch (error) {
18441
+ sendHttpError(res, error);
18442
+ }
18443
+ return true;
18444
+ }
18445
+ if (await handleFilesRoutes(req, res, url, method, API_PREFIX, fileGate)) return true;
18446
+ return false;
18447
+ }
18448
+ /**
18449
+ * SPA shell (`no-store`, so a reload never serves a stale bundle), content-
18450
+ * hashed `/assets/` with long-term caching, and the terminal 404. Always ends
18451
+ * the request, so it must be the last handler in the chain.
18452
+ */
18453
+ async function handleStaticRoutes(res, url, publicDir) {
18454
+ if (url === "/" || url === "/index.html") {
18455
+ try {
18456
+ const html = await readFile(join(publicDir, "index.html"), "utf-8");
18457
+ res.writeHead(200, {
18458
+ "Content-Type": "text/html; charset=utf-8",
18459
+ "Cache-Control": "no-store"
18460
+ });
18461
+ res.end(html);
18462
+ } catch {
18463
+ res.writeHead(500, { "Content-Type": "text/plain" });
18464
+ res.end("Failed to load web UI. Did you run pnpm web:build?");
18465
+ }
18466
+ return;
18467
+ }
18468
+ const safeUrl = url.replaceAll(/\?.*$/g, "").replaceAll(/\.{2,}/g, "");
18469
+ try {
18470
+ const filePath = join(publicDir, safeUrl);
18471
+ const contentType = contentTypes[filePath.split(".").pop() ?? ""] ?? "application/octet-stream";
18472
+ const data = await readFile(filePath);
18473
+ const headers = { "Content-Type": contentType };
18474
+ if (safeUrl.startsWith("/assets/")) headers["Cache-Control"] = "public, max-age=31536000, immutable";
18475
+ res.writeHead(200, headers);
18476
+ res.end(data);
18477
+ } catch {
18478
+ res.writeHead(404, { "Content-Type": "text/plain" });
18479
+ res.end("Not found");
18480
+ }
18481
+ }
18482
+ /**
18346
18483
  * Watchdog shared by both web entry points. Detects "nobody is using the UI"
18347
18484
  * as: zero WebSocket connections AND no HTTP request for `idleMs`, with an
18348
18485
  * in-flight agent turn (`busy()`) deferring the exit so background work is
@@ -18894,84 +19031,7 @@ async function runWebServer(opts) {
18894
19031
  if (await handleSessionControlRoutes(req, res, url, method, manager)) return;
18895
19032
  if (await handleResourceRoutes(req, res, url, method, manager)) return;
18896
19033
  if (await handleGlobalRoutes(req, res, url, method, manager)) return;
18897
- const snapshotMatch = new RegExp(`^${API_PREFIX}/sessions/([^/]+)/snapshot(\\?|$)`).exec(url);
18898
- if (snapshotMatch && method === "GET") {
18899
- const sessionId = decodeURIComponent(snapshotMatch[1]);
18900
- const ws = manager.get(sessionId);
18901
- if (!ws) {
18902
- res.writeHead(404, { "Content-Type": "application/json" });
18903
- res.end(JSON.stringify({
18904
- code: 404,
18905
- message: "Session not found"
18906
- }));
18907
- return;
18908
- }
18909
- try {
18910
- await ws.ready;
18911
- const query = new URLSearchParams(url.split("?")[1] ?? "");
18912
- const tail = Number(query.get("tail") ?? "");
18913
- sendJson(res, 200, Number.isFinite(tail) && tail > 0 ? ws.getSnapshot({ tail }) : ws.getSnapshot());
18914
- } catch (error) {
18915
- sendHttpError(res, error);
18916
- }
18917
- return;
18918
- }
18919
- const olderMatch = /^\/api\/v1\/sessions\/([^/?]+)\/messages(\?|$)/.exec(url);
18920
- if (olderMatch && method === "GET") {
18921
- const sessionId = decodeURIComponent(olderMatch[1]);
18922
- const ws = manager.get(sessionId);
18923
- if (!ws) {
18924
- res.writeHead(404, { "Content-Type": "application/json" });
18925
- res.end(JSON.stringify({
18926
- code: 404,
18927
- message: "Session not found"
18928
- }));
18929
- return;
18930
- }
18931
- try {
18932
- await ws.ready;
18933
- const query = new URLSearchParams(url.split("?")[1] ?? "");
18934
- const seq = Number(query.get("seq") ?? "0");
18935
- const tool = query.get("tool") ?? "";
18936
- if (Number.isFinite(seq) && seq > 0 && tool) {
18937
- sendJson(res, 200, { output: ws.getThinkingEntry(seq, tool) });
18938
- return;
18939
- }
18940
- const before = Number(query.get("before") ?? "0");
18941
- const tail = Number(query.get("tail") ?? "50");
18942
- sendJson(res, 200, Number.isFinite(before) && before > 0 ? ws.getMessagesOlder(before, Number.isFinite(tail) && tail > 0 ? tail : 50) : {
18943
- messages: [],
18944
- hasMore: false
18945
- });
18946
- } catch (error) {
18947
- sendHttpError(res, error);
18948
- }
18949
- return;
18950
- }
18951
- if (url === `${API_PREFIX}/git/status` && method === "GET") {
18952
- const gs = await getGitStatus(workDir);
18953
- res.writeHead(200, { "Content-Type": "application/json" });
18954
- res.end(JSON.stringify(gs));
18955
- return;
18956
- }
18957
- if (url.startsWith(`${API_PREFIX}/git/diff?`) && method === "GET") {
18958
- const relPath = new URLSearchParams(url.split("?")[1] ?? "").get("path") ?? "";
18959
- try {
18960
- const result = await getGitFileDiff(workDir, relPath);
18961
- res.writeHead(200, { "Content-Type": "application/json" });
18962
- res.end(JSON.stringify({
18963
- path: relPath,
18964
- patch: result?.patch ?? ""
18965
- }));
18966
- } catch (error) {
18967
- sendHttpError(res, error);
18968
- }
18969
- return;
18970
- }
18971
- function makeFileGate() {
18972
- return new FileGate(() => manager.list().map((s) => s.workDir));
18973
- }
18974
- if (await handleFilesRoutes(req, res, url, method, API_PREFIX, makeFileGate())) return;
19034
+ if (await handleSessionDataRoutes(req, res, url, method, (sessionId) => manager.get(sessionId), workDir, new FileGate(() => manager.list().map((s) => s.workDir)))) return;
18975
19035
  if (url === `${API_PREFIX}/like` && method === "GET") {
18976
19036
  try {
18977
19037
  sendJson(res, 200, await loadLikePreferences());
@@ -19179,33 +19239,7 @@ async function runWebServer(opts) {
19179
19239
  }
19180
19240
  return;
19181
19241
  }
19182
- if (url === "/" || url === "/index.html") {
19183
- try {
19184
- const html = await readFile(join(publicDir, "index.html"), "utf-8");
19185
- res.writeHead(200, {
19186
- "Content-Type": "text/html; charset=utf-8",
19187
- "Cache-Control": "no-store"
19188
- });
19189
- res.end(html);
19190
- } catch {
19191
- res.writeHead(500, { "Content-Type": "text/plain" });
19192
- res.end("Failed to load web UI. Did you run pnpm web:build?");
19193
- }
19194
- return;
19195
- }
19196
- const safeUrl = url.replaceAll(/\?.*$/g, "").replaceAll(/\.{2,}/g, "");
19197
- try {
19198
- const filePath = join(publicDir, safeUrl);
19199
- const contentType = contentTypes[filePath.split(".").pop() ?? ""] ?? "application/octet-stream";
19200
- const data = await readFile(filePath);
19201
- const headers = { "Content-Type": contentType };
19202
- if (safeUrl.startsWith("/assets/")) headers["Cache-Control"] = "public, max-age=31536000, immutable";
19203
- res.writeHead(200, headers);
19204
- res.end(data);
19205
- } catch {
19206
- res.writeHead(404, { "Content-Type": "text/plain" });
19207
- res.end("Not found");
19208
- }
19242
+ await handleStaticRoutes(res, url, publicDir);
19209
19243
  });
19210
19244
  const wss = new WebSocketServer({ server: httpServer });
19211
19245
  wss.on("error", () => {});
@@ -3,5 +3,5 @@ import { fileURLToPath as __cjsShimFileURLToPath } from 'node:url';
3
3
  import { dirname as __cjsShimDirname } from 'node:path';
4
4
  const __filename = __cjsShimFileURLToPath(import.meta.url);
5
5
  const __dirname = __cjsShimDirname(__filename);
6
- import { nn as handleConnectCommand } from "./dispatch-ChDxIz0h.mjs";
6
+ import { nn as handleConnectCommand } from "./dispatch-La1sAZPF.mjs";
7
7
  export { handleConnectCommand };
@@ -6,7 +6,7 @@ const __dirname = __cjsShimDirname(__filename);
6
6
  import { i as __require, o as __toESM, r as __exportAll, t as __commonJSMin } from "./chunk-D90kvbyJ.mjs";
7
7
  import { C as join$1, D as resolve$2, E as relative$1, S as isAbsolute$1, T as parse$7, a as isSupportedFile, b as basename$1, i as ingestFile, r as ingestDirectory, t as multiSearch, w as normalize$1, x as dirname$2, y as KnowledgeStore } from "./src-tDEINaMV.mjs";
8
8
  import { t as require_base64_js } from "./base64-js-DzVmk6Nb.mjs";
9
- import { a as setLocale, i as getLocale, n as assertScreamHostIdentity, o as t, r as createScreamDefaultHeaders, t as TextInputDialogComponent } from "./text-input-dialog-GajpxjyL.mjs";
9
+ import { a as setLocale, i as getLocale, n as assertScreamHostIdentity, o as t, r as createScreamDefaultHeaders, t as TextInputDialogComponent } from "./text-input-dialog--J3ngWFv.mjs";
10
10
  import { createRequire } from "node:module";
11
11
  import { createHash, randomBytes, randomInt, randomUUID } from "node:crypto";
12
12
  import * as fs$1 from "node:fs/promises";
@@ -108049,7 +108049,7 @@ var Session$1 = class {
108049
108049
  parents: true,
108050
108050
  existOk: true
108051
108051
  });
108052
- await this.options.jian.writeText(this.metadataPath, text);
108052
+ await this.options.jian.writeTextAtomic(this.metadataPath, text);
108053
108053
  };
108054
108054
  this.writeMetadataPromise = this.writeMetadataPromise.then(() => write(), () => write());
108055
108055
  return this.writeMetadataPromise;
@@ -124628,6 +124628,7 @@ var LocalProcess = class {
124628
124628
  return Promise.resolve();
124629
124629
  }
124630
124630
  };
124631
+ let atomicWriteCounter = 0;
124631
124632
  /**
124632
124633
  * A JIAN implementation that directly interacts with the local filesystem.
124633
124634
  *
@@ -124876,6 +124877,14 @@ var LocalJian = class LocalJian {
124876
124877
  else await writeFile(resolved, data, encoding);
124877
124878
  return data.length;
124878
124879
  }
124880
+ async writeTextAtomic(path, data, options) {
124881
+ const resolved = this._resolvePath(path);
124882
+ const encoding = options?.encoding ?? "utf-8";
124883
+ const tmpPath = `${resolved}.${process.pid}.${Date.now().toString(36)}.${(atomicWriteCounter++).toString(36)}.tmp`;
124884
+ await writeFile(tmpPath, data, encoding);
124885
+ await rename(tmpPath, resolved);
124886
+ return data.length;
124887
+ }
124879
124888
  async mkdir(path, options) {
124880
124889
  const resolved = this._resolvePath(path);
124881
124890
  const parents = options?.parents ?? false;
@@ -131800,6 +131809,7 @@ const darkColors = {
131800
131809
  mdCodeBlockBorder: "#5C6370",
131801
131810
  mdCodeBlockBg: "#16191f",
131802
131811
  mdQuote: "#7F848E",
131812
+ mdHeading: "#FFC35C",
131803
131813
  border: dark.gray700,
131804
131814
  borderFocus: dark.yellowGreenLight,
131805
131815
  success: dark.yellowGreen,
@@ -131834,6 +131844,7 @@ const lightColors = {
131834
131844
  mdCodeBlockBorder: "#848484",
131835
131845
  mdCodeBlockBg: "#f7f9fb",
131836
131846
  mdQuote: "#616161",
131847
+ mdHeading: "#8F6400",
131837
131848
  border: light.gray500,
131838
131849
  borderFocus: light.yellowGreen700,
131839
131850
  success: light.yellowGreen700,
@@ -132017,16 +132028,16 @@ const HEADING_HASH_PREFIX = /^((?:\u001B\[[0-9;]*m)*)#{1,6}[ \t]+/;
132017
132028
  * its DEFAULT_THEME.
132018
132029
  */
132019
132030
  /**
132020
- * Markdown code-block highlight theme: green-dominant mapping (keyword,
132021
- * function, built_in primary; strings success; numbers → warning;
132022
- * comments → textDim). Kept distinct from the shared preview theme in
132023
- * code-highlight-theme.ts on purpose — markdown code blocks use the green
132031
+ * Markdown code-block highlight theme: the fluorescent brand green is the
132032
+ * only green (keyword, function, built_in, diff additions); strings take the
132033
+ * heading amber; numbers → warning; comments → textDim. Kept distinct from
132034
+ * the shared preview theme in code-highlight-theme.ts on purpose.
132024
132035
  * primary hue, while file-preview panels use the classic blue/red/yellow
132025
132036
  * scheme mapped to the same palette. Both follow the active theme.
132026
132037
  */
132027
132038
  function createMarkdownCodeHighlightTheme(colors) {
132028
132039
  const keyword = chalk.hex(colors.primary);
132029
- const str = chalk.hex(colors.success);
132040
+ const str = chalk.hex(colors.mdHeading);
132030
132041
  const comment = chalk.hex(colors.textDim);
132031
132042
  const num = chalk.hex(colors.warning);
132032
132043
  const fn = chalk.hex(colors.primary);
@@ -132065,7 +132076,7 @@ function createMarkdownCodeHighlightTheme(colors) {
132065
132076
  formula: text,
132066
132077
  link: chalk.hex(colors.mdLink),
132067
132078
  quote: chalk.hex(colors.mdQuote),
132068
- addition: chalk.hex(colors.diffAdded),
132079
+ addition: keyword,
132069
132080
  deletion: chalk.hex(colors.diffRemoved),
132070
132081
  default: text
132071
132082
  };
@@ -132076,7 +132087,7 @@ function createMarkdownTheme(colors) {
132076
132087
  const border = chalk.hex(colors.border);
132077
132088
  const codeTheme = createMarkdownCodeHighlightTheme(colors);
132078
132089
  return {
132079
- heading: (text) => chalk.bold.hex(colors.text)(stripHash(text)),
132090
+ heading: (text) => chalk.bold.hex(colors.mdHeading)(stripHash(text)),
132080
132091
  link: (text) => chalk.hex(colors.mdLink)(text),
132081
132092
  linkUrl: (text) => muted(text),
132082
132093
  code: (text) => chalk.hex(colors.primary)(text),
@@ -134054,7 +134065,7 @@ async function guidedGoalSetup(host) {
134054
134065
  host.showNotice(t("goal.storm_breaker"), t("goal.conflict_loop"));
134055
134066
  return;
134056
134067
  }
134057
- const { TextInputDialogComponent } = await import("./text-input-dialog-DAQfJdHu.mjs");
134068
+ const { TextInputDialogComponent } = await import("./text-input-dialog-CNhL-3wm.mjs");
134058
134069
  const initialDesc = await promptText(host, TextInputDialogComponent, {
134059
134070
  title: t("goal.setup_title_initial"),
134060
134071
  subtitle: t("goal.setup_desc_hint"),
@@ -134075,7 +134086,7 @@ async function guidedGoalSetup(host) {
134075
134086
  await showGoalConfigWizard(host, session, confirmed.trim() || objective, false);
134076
134087
  }
134077
134088
  async function showGoalConfigWizard(host, session, objective, replace) {
134078
- const { TextInputDialogComponent } = await import("./text-input-dialog-DAQfJdHu.mjs");
134089
+ const { TextInputDialogComponent } = await import("./text-input-dialog-CNhL-3wm.mjs");
134079
134090
  const turnInput = await promptNumber(host, TextInputDialogComponent, {
134080
134091
  title: t("goal.wizard_title", { objective }),
134081
134092
  subtitle: t("goal.budget_turns_hint"),
@@ -134989,10 +135000,9 @@ const ACTIVITY_LINE_SETTINGS = [
134989
135000
  key: "activityCollapsedLines",
134990
135001
  label: "blockrows.item_collapsed",
134991
135002
  values: [
134992
- 2,
134993
135003
  3,
134994
- 4,
134995
- 5
135004
+ 5,
135005
+ 8
134996
135006
  ],
134997
135007
  fallback: 3
134998
135008
  },
@@ -135233,10 +135243,6 @@ const CARD_MARKERS = [
135233
135243
  const CARD_MARKER_AT_START_RE = new RegExp(`^(?:\\u001b\\[[0-9;]*m)*(?:${CARD_MARKERS.join("|")}) `);
135234
135244
  const CARD_MARKER_CHAR_RE = new RegExp(`^(?:\\u001b\\[[0-9;]*m)*(${CARD_MARKERS.join("|")}) `);
135235
135245
  const CARD_INDENT_RE = /^(\u001B\[[0-9;]*m)*( {2})/;
135236
- /** Token count for a text block; empty text is zero, unlike the rate estimator. */
135237
- function countTokens(text) {
135238
- return text.trim().length === 0 ? 0 : estimateTokens(text);
135239
- }
135240
135246
  /** Non-empty, trimmed lines of one reasoning run. */
135241
135247
  function thinkingLines(text) {
135242
135248
  return text.split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0);
@@ -135393,10 +135399,6 @@ var ActivityGroupComponent = class extends Container {
135393
135399
  spinnerFrame = 0;
135394
135400
  lastSpinnerTickAt = 0;
135395
135401
  renderedWidth;
135396
- /** Token estimates per borrowed card, keyed by the result text they counted. */
135397
- toolTokenCache = /* @__PURE__ */ new WeakMap();
135398
- /** Token estimates per reasoning run, keyed by the segment they counted. */
135399
- thinkingTokenCache = /* @__PURE__ */ new WeakMap();
135400
135402
  /** Reasoning text already shown when a notice interrupted the newest run. */
135401
135403
  interruptedThinking;
135402
135404
  constructor(colors, ui) {
@@ -135558,6 +135560,28 @@ var ActivityGroupComponent = class extends Container {
135558
135560
  if (this.expanded) this.buildExpandedRows(width);
135559
135561
  else this.buildCollapsedRows(width);
135560
135562
  }
135563
+ /**
135564
+ * Aggregate diff of the group's finished, successful file mutations
135565
+ * (Edit/Write), shown in the header. Read-only tools contribute nothing;
135566
+ * failed attempts changed nothing, so only successful calls count.
135567
+ */
135568
+ diffTotals() {
135569
+ let added = 0;
135570
+ let removed = 0;
135571
+ let seen = false;
135572
+ for (const segment of this.segments) {
135573
+ if (segment.kind !== "tool") continue;
135574
+ const contribution = segment.tc.diffContribution();
135575
+ if (contribution === void 0) continue;
135576
+ seen = true;
135577
+ added += contribution.added;
135578
+ removed += contribution.removed;
135579
+ }
135580
+ return seen ? {
135581
+ added,
135582
+ removed
135583
+ } : void 0;
135584
+ }
135561
135585
  buildHeader(width) {
135562
135586
  const colors = this.colors;
135563
135587
  const frame = BRAILLE_SPINNER_FRAMES[this.spinnerFrame] ?? BRAILLE_SPINNER_FRAMES[0];
@@ -135567,13 +135591,17 @@ var ActivityGroupComponent = class extends Container {
135567
135591
  const toolCount = this.toolCount();
135568
135592
  if (this.steps.size > 0) parts.push(t("activitygroup.steps", { count: String(this.steps.size) }));
135569
135593
  if (toolCount > 0) parts.push(t("activitygroup.tools", { count: String(toolCount) }));
135570
- parts.push(t("activitygroup.tokens", { tok: this.formatTokens(this.blockTokens()) }));
135571
135594
  const stats = chalk.dim(SEPARATOR + parts.join(SEPARATOR));
135595
+ const diff = this.diffTotals();
135596
+ const diffPart = diff ? SEPARATOR + t("activitygroup.diff", {
135597
+ added: chalk.hex(colors.diffAdded)(`+${diff.added}`),
135598
+ removed: chalk.hex(colors.diffRemoved)(`-${diff.removed}`)
135599
+ }) : "";
135572
135600
  const speed = this.liveThinking() ? getSharedSpeedTracker().getSpeed() : 0;
135573
135601
  const rate = speed > MIN_RATE ? chalk.hex(lerpHex(colors.textDim, colors.primary, easeSpeedRatio(speed / 200)))(t("activitygroup.rate", { rate: speed.toFixed(1) })) : chalk.dim(t("activitygroup.rate", { rate: "-" }));
135574
135602
  const hint = chalk.dim(this.expanded ? t("activitygroup.hint_collapse") : t("activitygroup.hint_expand"));
135575
135603
  const head = `${marker}${chalk.hex(colors.primary).bold(label)}${stats}`;
135576
- const withRate = `${head}${rate}`;
135604
+ const withRate = `${head}${diffPart}${rate}`;
135577
135605
  const full = `${withRate}${hint}`;
135578
135606
  if (visibleWidth(full) <= width) return full;
135579
135607
  if (visibleWidth(withRate) <= width) return withRate;
@@ -135748,53 +135776,10 @@ var ActivityGroupComponent = class extends Container {
135748
135776
  const first = thinkingLines(segment.text)[0] ?? "";
135749
135777
  const label = t("activitygroup.thinking_summary", { summary: "" });
135750
135778
  const used = visibleWidth(isLast ? BRANCH_LAST : BRANCH_FIRST) + visibleWidth(label);
135751
- const cells = Math.max(1, width - used);
135779
+ const cells = Math.max(1, width - used - 1);
135752
135780
  const summary = chalk.hex(this.colors.roleThinking)(t("activitygroup.thinking_summary", { summary: truncateToWidth(first, cells, "…") }));
135753
135781
  return new Text(`${isLast ? BRANCH_LAST : BRANCH_FIRST}${summary}`, 0, 0);
135754
135782
  }
135755
- /**
135756
- * Token estimate for the whole block: the reasoning text plus every result the
135757
- * block owns, i.e. how much material this stretch of work moved through.
135758
- */
135759
- blockTokens() {
135760
- let tokens = 0;
135761
- for (const segment of this.segments) if (segment.kind === "tool") tokens += this.toolTokens(segment.tc);
135762
- else if (segment.kind === "thinking") tokens += this.thinkingTokens(segment);
135763
- return tokens;
135764
- }
135765
- /** Result tokens of one card, counted once per distinct result text. */
135766
- toolTokens(tc) {
135767
- const output = tc.resultView?.output ?? "";
135768
- const cached = this.toolTokenCache.get(tc);
135769
- if (cached !== void 0 && cached.output === output) return cached.tokens;
135770
- const tokens = countTokens(output);
135771
- this.toolTokenCache.set(tc, {
135772
- output,
135773
- tokens
135774
- });
135775
- return tokens;
135776
- }
135777
- /**
135778
- * Token estimate for the block's reasoning. Character-based and script-aware
135779
- * (CJK ≈ one token per char, Latin ≈ a quarter), using the same estimator as
135780
- * the streaming speed gauge so both numbers agree. Empty reasoning counts as
135781
- * zero: the estimator's floor of one token is for per-delta rates.
135782
- */
135783
- thinkingTokens(segment) {
135784
- const cached = this.thinkingTokenCache.get(segment);
135785
- if (cached !== void 0 && cached.text === segment.text) return cached.tokens;
135786
- const tokens = countTokens(segment.text);
135787
- this.thinkingTokenCache.set(segment, {
135788
- text: segment.text,
135789
- tokens
135790
- });
135791
- return tokens;
135792
- }
135793
- formatTokens(tokens) {
135794
- if (tokens < 1e3) return String(tokens);
135795
- if (tokens < 1e6) return `${(tokens / 1e3).toFixed(1)}K`;
135796
- return `${(tokens / 1e6).toFixed(1)}M`;
135797
- }
135798
135783
  scheduleSpinnerTick(delayMs) {
135799
135784
  if (this.ui === void 0) return;
135800
135785
  const timer = setTimeout(() => {
@@ -138018,6 +138003,28 @@ var ToolCallComponent = class ToolCallComponent extends CachedContainer {
138018
138003
  this.rebuildBody();
138019
138004
  this.notifySnapshotChange();
138020
138005
  }
138006
+ /**
138007
+ * File-mutation diff of this call (Edit additions/deletions, Write
138008
+ * additions), for the group-level diff stat in the activity block header.
138009
+ * Undefined for non-mutating tools, unfinished calls, and failed calls: a
138010
+ * failure changed nothing, so it must not count towards the group total.
138011
+ */
138012
+ diffContribution() {
138013
+ if (this.result === void 0 || this.result.is_error) return void 0;
138014
+ if (this.toolCall.name === "Edit") {
138015
+ const stats = computeEditStats(this.toolCall.args);
138016
+ if (stats.added === 0 && stats.removed === 0) return void 0;
138017
+ return stats;
138018
+ }
138019
+ if (this.toolCall.name === "Write") {
138020
+ const stats = computeWriteStats(this.toolCall.args);
138021
+ if (stats.lines === 0) return void 0;
138022
+ return {
138023
+ added: stats.lines,
138024
+ removed: 0
138025
+ };
138026
+ }
138027
+ }
138021
138028
  updateToolCall(toolCall) {
138022
138029
  this.toolCall = toolCall;
138023
138030
  this.syncStreamingProgressTimer();
package/dist/main.mjs CHANGED
@@ -6,7 +6,7 @@ const __dirname = __cjsShimDirname(__filename);
6
6
  import "./suppress-sqlite-warning-C2VB0doZ.mjs";
7
7
  //#region src/main.ts
8
8
  try {
9
- (await import("./app-C1foLjDE.mjs")).main();
9
+ (await import("./app-C0OLgL_b.mjs")).main();
10
10
  } catch (error) {
11
11
  process.stderr.write(`${error instanceof Error ? error.stack ?? error.message : String(error)}\n`);
12
12
  process.exit(1);
@@ -1040,8 +1040,8 @@ const dictionaries = {
1040
1040
  "activitygroup.done": "工具执行完成",
1041
1041
  "activitygroup.steps": "{count} 步",
1042
1042
  "activitygroup.tools": "{count} 个工具",
1043
- "activitygroup.tokens": "{tok} tok",
1044
- "activitygroup.rate": " · {rate} toks/s",
1043
+ "activitygroup.diff": "{added} {removed}",
1044
+ "activitygroup.rate": " · 实速 {rate} toks/s",
1045
1045
  "activitygroup.hint_expand": " (ctrl+o 展开)",
1046
1046
  "activitygroup.hint_collapse": " (ctrl+o 收起)",
1047
1047
  "activitygroup.segments_hidden": "… 还有 {count} 步",
@@ -2248,8 +2248,8 @@ const dictionaries = {
2248
2248
  "activitygroup.done": "Tool calls finished",
2249
2249
  "activitygroup.steps": "{count} steps",
2250
2250
  "activitygroup.tools": "{count} tools",
2251
- "activitygroup.tokens": "{tok} tok",
2252
- "activitygroup.rate": " · {rate} toks/s",
2251
+ "activitygroup.diff": "{added} {removed}",
2252
+ "activitygroup.rate": " · live {rate} toks/s",
2253
2253
  "activitygroup.hint_expand": " (ctrl+o to expand)",
2254
2254
  "activitygroup.hint_collapse": " (ctrl+o to collapse)",
2255
2255
  "activitygroup.segments_hidden": "… {count} more steps",
@@ -3,5 +3,5 @@ import { fileURLToPath as __cjsShimFileURLToPath } from 'node:url';
3
3
  import { dirname as __cjsShimDirname } from 'node:path';
4
4
  const __filename = __cjsShimFileURLToPath(import.meta.url);
5
5
  const __dirname = __cjsShimDirname(__filename);
6
- import { t as TextInputDialogComponent } from "./text-input-dialog-GajpxjyL.mjs";
6
+ import { t as TextInputDialogComponent } from "./text-input-dialog--J3ngWFv.mjs";
7
7
  export { TextInputDialogComponent };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "scream-code",
3
- "version": "0.16.3",
3
+ "version": "0.16.4",
4
4
  "description": "A terminal-native AI agent for builders",
5
5
  "license": "MIT",
6
6
  "author": "ScreamCli",