@code-yeongyu/senpi-codemode 2026.10.10-4 → 2026.10.10-5

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.
@@ -1,7 +1,6 @@
1
1
  import type { AgentToolResult } from "@code-yeongyu/senpi";
2
2
  import { type CellDeadlineExpiry, CellDeadlines } from "./cell-deadlines.ts";
3
3
  import type { EvalDetachedCellSnapshot, EvalDetachedCellState } from "./detached-cell-contract.ts";
4
- import { detachedNotificationSpillPath } from "./detached-cell-notification.ts";
5
4
  import type { EvalKernel, EvalToolDetails, EvalToolInput } from "./types.ts";
6
5
 
7
6
  export type LiveResultProvider = () => AgentToolResult<EvalToolDetails>;
@@ -9,7 +8,6 @@ export type LiveResultProvider = () => AgentToolResult<EvalToolDetails>;
9
8
  export type ManagedCell = {
10
9
  readonly cellId: string;
11
10
  readonly input: EvalToolInput;
12
- readonly spillPath: string | undefined;
13
11
  readonly startedAtMs: number;
14
12
  readonly terminal: PromiseWithResolvers<EvalDetachedCellSnapshot>;
15
13
  state: Exclude<EvalDetachedCellState, "detached">;
@@ -37,7 +35,6 @@ export type ManagedCell = {
37
35
  export interface ManagedCellInit {
38
36
  readonly cellId: string;
39
37
  readonly input: EvalToolInput;
40
- readonly artifactsDir: string | undefined;
41
38
  readonly now: () => number;
42
39
  readonly defaultHardLimitSeconds: number;
43
40
  readonly defaultRunBudgetSeconds: number;
@@ -60,7 +57,6 @@ export function createManagedCell(init: ManagedCellInit): ManagedCell {
60
57
  return {
61
58
  cellId: init.cellId,
62
59
  input: init.input,
63
- spillPath: detachedNotificationSpillPath(init.artifactsDir, init.cellId),
64
60
  startedAtMs: init.now(),
65
61
  state: "queued",
66
62
  runStartedAtMs: undefined,
@@ -7,6 +7,7 @@ import {
7
7
  type ToolDefinition,
8
8
  type ToolRenderResultOptions,
9
9
  truncateToVisualLines,
10
+ visibleWidth,
10
11
  } from "@code-yeongyu/senpi";
11
12
  import type { TruncationMeta } from "../output/output-meta.ts";
12
13
  import { highlightedCode } from "./code-preview.ts";
@@ -21,6 +22,7 @@ import {
21
22
  JSON_TREE_SCALAR_LEN_EXPANDED,
22
23
  renderJsonTreeLines,
23
24
  } from "./json-tree.ts";
25
+ import { knownLanguage, leadsWithHeadline, liveHeadline } from "./live-headline.ts";
24
26
  import { formatRuntimeBadge } from "./runtime-label.ts";
25
27
  import { codePointPrefix, formatDuration, renderToolCallWidget } from "./tool-widgets.ts";
26
28
  import type {
@@ -341,7 +343,9 @@ function cellHeader(cell: EvalCellResult, environment: RenderEnvironment, badges
341
343
  environment.spinnerFrame ?? Math.floor((cellElapsedMs(cell, environment) ?? 0) / LIVE_RENDER_TICK_MS),
342
344
  );
343
345
  const runtimeBadge = cell.runtime === undefined ? "" : ` (${formatRuntimeBadge(cell.language, cell.runtime)})`;
344
- let header = `eval ${cell.language}${runtimeBadge} ${presentation.label} ${presentation.icon}`;
346
+ let header = leadsWithHeadline(cell.status)
347
+ ? `eval ${cell.language}${runtimeBadge} ${presentation.label}`
348
+ : `eval ${cell.language}${runtimeBadge} ${presentation.label} ${presentation.icon}`;
345
349
  if (cell.queuedBehind !== undefined && cell.queuedBehind.length > 0)
346
350
  header += ` · queued behind ${cell.queuedBehind.map(sanitizeTerminalLabel).join(", ")}`;
347
351
  else if (cell.queuedBehind !== undefined && cell.status === "queued")
@@ -352,9 +356,24 @@ function cellHeader(cell: EvalCellResult, environment: RenderEnvironment, badges
352
356
  if (elapsedMs !== undefined) header += ` · ${formatDuration(elapsedMs)}`;
353
357
  if (badges.reset) header += " · reset";
354
358
  if (badges.timeout !== undefined) header += ` · timeout ${badges.timeout}s`;
359
+ if (leadsWithHeadline(cell.status))
360
+ header = headlined(presentation.icon, cell.summary, cell.code, header, environment);
355
361
  return style(environment.theme, presentation.color, header);
356
362
  }
357
363
 
364
+ // An in-progress row leads with what the cell is doing (senpi#2802); collapsed, the headline is cut so the whole
365
+ // header stays on one line, and the code moves behind expand.
366
+ function headlined(
367
+ icon: string,
368
+ summary: string | undefined,
369
+ code: string | undefined,
370
+ rest: string,
371
+ environment: RenderEnvironment,
372
+ ): string {
373
+ const budget = environment.expanded ? undefined : environment.width - 3 - visibleWidth(`${icon} · ${rest}`);
374
+ return `${icon} ${liveHeadline(summary, code, budget)} · ${rest}`;
375
+ }
376
+
358
377
  function previewText(
359
378
  text: string,
360
379
  maxLines: number,
@@ -614,12 +633,23 @@ function renderAgentProgressEvents(events: readonly EvalStatusEvent[], environme
614
633
  }
615
634
 
616
635
  function renderCell(cell: EvalCellResult, environment: RenderEnvironment, badges: CellBadges): string[] {
636
+ if (leadsWithHeadline(cell.status) && !environment.expanded && cell.output.trim().length === 0) {
637
+ const statusEvents = (cell.statusEvents ?? []).filter((event) => event.op !== "agent");
638
+ if (statusEvents.length === 0) {
639
+ // Nothing to frame yet: the collapsed live row is its headline alone, one line, no empty box.
640
+ const lines = renderPrefixed(cellHeader(cell, environment, badges), environment, LIVE_LINE_PREFIX);
641
+ const agentEvents = (cell.statusEvents ?? []).filter((event) => event.op === "agent");
642
+ if (agentEvents.length > 0) appendLines(lines, renderAgentProgressEvents(agentEvents, environment));
643
+ return lines;
644
+ }
645
+ }
617
646
  const lines = renderPrefixed(cellHeader(cell, environment, badges), environment, {
618
647
  prefix: "╭─ ",
619
648
  continuation: "│ ",
620
649
  color: "borderAccent",
621
650
  });
622
- if (cell.summary !== undefined) {
651
+ const headlined = leadsWithHeadline(cell.status);
652
+ if (cell.summary !== undefined && !headlined) {
623
653
  appendLines(
624
654
  lines,
625
655
  summaryVisualLines(cell.summary, Math.max(1, environment.width - 2), environment.expanded).map(
@@ -628,11 +658,14 @@ function renderCell(cell: EvalCellResult, environment: RenderEnvironment, badges
628
658
  );
629
659
  }
630
660
  const innerWidth = Math.max(1, environment.width - 2);
631
- const codePreview = previewText(
632
- highlightedCode(cell.code, cell.language, environment.theme, environment.repaint),
633
- environment.expanded ? Number.POSITIVE_INFINITY : CODE_PREVIEW_LINES,
634
- innerWidth,
635
- );
661
+ const codePreview =
662
+ headlined && !environment.expanded
663
+ ? { lines: [], skipped: 0 }
664
+ : previewText(
665
+ highlightedCode(cell.code, cell.language, environment.theme, environment.repaint),
666
+ environment.expanded ? Number.POSITIVE_INFINITY : CODE_PREVIEW_LINES,
667
+ innerWidth,
668
+ );
636
669
  if (codePreview.skipped > 0) {
637
670
  appendLines(
638
671
  lines,
@@ -881,6 +914,10 @@ function displaySummary(summary: string | undefined): string | undefined {
881
914
  return normalizeEvalSummary(summary);
882
915
  }
883
916
 
917
+ function cellIdSuffix(cellId: unknown): string {
918
+ return typeof cellId === "string" && cellId !== "" ? ` ${sanitizeTerminalLabel(cellId)}` : "";
919
+ }
920
+
884
921
  export function renderEvalCall(
885
922
  args: EvalToolRequest,
886
923
  theme: Theme | undefined,
@@ -894,15 +931,23 @@ export function renderEvalCall(
894
931
  return component;
895
932
  }
896
933
  if (!isEvalRunInput(args)) {
897
- const title = args.action === "list" ? "eval list" : `eval ${args.action} ${args.cell_id}`;
934
+ // While a peek/stop call streams in, `cell_id` can still be missing; the title is the action alone until it arrives.
935
+ const title = args.action === "list" ? "eval list" : `eval ${args.action}${cellIdSuffix(args.cell_id)}`;
898
936
  component.setBlocks([{ kind: "text", text: style(theme, "toolTitle", title) }]);
899
937
  return component;
900
938
  }
939
+ // While the model streams the call, `code` usually arrives before `language` and `summary`; a partial call still
940
+ // renders (the host would otherwise fall back to a raw key=value row).
941
+ const code = typeof args.code === "string" ? args.code : "";
942
+ const language = knownLanguage(args.language);
901
943
  if (theme === undefined && context.spinnerFrame === undefined) {
902
944
  const reset = args.reset === true ? " reset" : "";
903
945
  const timeout = args.timeout === undefined ? "" : ` timeout ${args.timeout}s`;
904
946
  component.setBlocks([
905
- { kind: "text", text: style(theme, "toolTitle", `eval ${args.language}${reset}${timeout}`) },
947
+ {
948
+ kind: "text",
949
+ text: style(theme, "toolTitle", `eval${language === undefined ? "" : ` ${language}`}${reset}${timeout}`),
950
+ },
906
951
  ...(displaySummary(args.summary) === undefined
907
952
  ? []
908
953
  : [summaryBlock(displaySummary(args.summary) ?? "", theme, context.expanded)]),
@@ -911,9 +956,11 @@ export function renderEvalCall(
911
956
  text: style(
912
957
  theme,
913
958
  "mdCodeBlock",
914
- args.code.trim().length > 0
915
- ? displayCode(args.code, args.language, context.argsComplete ? context.invalidate : undefined)
916
- : "...",
959
+ code.trim().length === 0
960
+ ? "..."
961
+ : language === undefined
962
+ ? code
963
+ : displayCode(code, language, context.argsComplete ? context.invalidate : undefined),
917
964
  ),
918
965
  maxVisualLines: context.expanded ? undefined : CODE_PREVIEW_LINES,
919
966
  collapseKind: "code",
@@ -935,11 +982,13 @@ export function renderEvalCall(
935
982
  now: renderNow(context),
936
983
  ...(context.argsComplete ? { repaint: context.invalidate } : {}),
937
984
  };
985
+ const summary = displaySummary(args.summary);
986
+ if (language === undefined) return streamingCallLines(summary, code, environment);
938
987
  const cell: EvalCellResult = {
939
988
  index: 0,
940
- ...(displaySummary(args.summary) === undefined ? {} : { summary: displaySummary(args.summary) ?? "" }),
941
- code: args.code,
942
- language: args.language,
989
+ ...(summary === undefined ? {} : { summary }),
990
+ code,
991
+ language,
943
992
  output: "",
944
993
  status: context.spinnerFrame === undefined ? "pending" : "running",
945
994
  };
@@ -954,6 +1003,14 @@ export function renderEvalCall(
954
1003
  return component;
955
1004
  }
956
1005
 
1006
+ /** A call whose language has not streamed in yet: the headline row alone, until the full frame can render. */
1007
+ function streamingCallLines(summary: string | undefined, code: string, environment: RenderEnvironment): string[] {
1008
+ const icon = environment.spinnerFrame === undefined ? "○" : spinner(environment.spinnerFrame);
1009
+ return renderPrefixed(headlined(icon, summary, code, "eval", environment), environment, LIVE_LINE_PREFIX);
1010
+ }
1011
+
1012
+ const LIVE_LINE_PREFIX: PrefixStyle = { prefix: "╶─ ", continuation: " ", color: "borderAccent" };
1013
+
957
1014
  export function renderEvalResult(
958
1015
  result: AgentToolResult<EvalResultDetails>,
959
1016
  options: ToolRenderResultOptions,