topchester-ai 0.27.0 → 0.28.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.mjs CHANGED
@@ -17,6 +17,7 @@ import pino from "pino";
17
17
  import picomatch from "picomatch";
18
18
  import { uuidv7 } from "uuidv7";
19
19
  import { CURSOR_MARKER, Markdown, ProcessTerminal, TUI, decodeKittyPrintable, isKeyRelease, isKeyRepeat, matchesKey, truncateToWidth, visibleWidth, wrapTextWithAnsi } from "@earendil-works/pi-tui";
20
+ import { diffWords } from "diff";
20
21
  import { highlight, supportsLanguage } from "cli-highlight";
21
22
  //#region src/agent/tools/ai-sdk-tools.ts
22
23
  function toAiSdkToolSet(definitions) {
@@ -3088,6 +3089,10 @@ const ui = {
3088
3089
  error(text) {
3089
3090
  return color(text, "red");
3090
3091
  },
3092
+ inverse(text) {
3093
+ if (!shouldUseColor()) return text;
3094
+ return `\u001b[7m${text}\u001b[27m`;
3095
+ },
3091
3096
  softBackground(text) {
3092
3097
  return color(text, "bgSoftGray");
3093
3098
  },
@@ -7556,6 +7561,85 @@ const queryStopWords = new Set([
7556
7561
  "when"
7557
7562
  ]);
7558
7563
  //#endregion
7564
+ //#region src/tui/diff.ts
7565
+ function renderUnifiedDiff(diffText, options = {}) {
7566
+ const indent = options.indent ?? "";
7567
+ const wrapWidth = options.width === void 0 ? void 0 : Math.max(1, options.width - indent.length);
7568
+ const rawLines = diffText.split("\n");
7569
+ const rendered = [];
7570
+ let index = 0;
7571
+ while (index < rawLines.length) {
7572
+ const line = rawLines[index];
7573
+ if (isRemovedBodyLine(line)) {
7574
+ const removed = [];
7575
+ while (index < rawLines.length && isRemovedBodyLine(rawLines[index])) {
7576
+ removed.push({
7577
+ kind: "removed",
7578
+ content: rawLines[index].slice(1)
7579
+ });
7580
+ index += 1;
7581
+ }
7582
+ const added = [];
7583
+ while (index < rawLines.length && isAddedBodyLine(rawLines[index])) {
7584
+ added.push({
7585
+ kind: "added",
7586
+ content: rawLines[index].slice(1)
7587
+ });
7588
+ index += 1;
7589
+ }
7590
+ rendered.push(...renderChangedLines(removed, added, indent, wrapWidth));
7591
+ continue;
7592
+ }
7593
+ if (isAddedBodyLine(line)) rendered.push(...renderWrappedLine(ui.ok(`+${expandTabs$1(line.slice(1))}`), indent, wrapWidth));
7594
+ else if (line.startsWith("@@")) rendered.push(...renderWrappedLine(ui.model(line), indent, wrapWidth));
7595
+ else if (line.startsWith("diff --git ") || line.startsWith("--- ") || line.startsWith("+++ ")) rendered.push(...renderWrappedLine(ui.muted(line), indent, wrapWidth));
7596
+ else if (line.startsWith(" ")) rendered.push(...renderWrappedLine(ui.label(` ${expandTabs$1(line.slice(1))}`), indent, wrapWidth));
7597
+ else if (line.length > 0) rendered.push(...renderWrappedLine(ui.label(line), indent, wrapWidth));
7598
+ index += 1;
7599
+ }
7600
+ return rendered;
7601
+ }
7602
+ function renderChangedLines(removedLines, addedLines, indent, wrapWidth) {
7603
+ if (removedLines.length === 1 && addedLines.length === 1) {
7604
+ const { removed, added } = renderIntraLineDiff(expandTabs$1(removedLines[0].content), expandTabs$1(addedLines[0].content));
7605
+ return [...renderWrappedLine(ui.error(`-${removed}`), indent, wrapWidth), ...renderWrappedLine(ui.ok(`+${added}`), indent, wrapWidth)];
7606
+ }
7607
+ return [...removedLines.flatMap((line) => renderWrappedLine(ui.error(`-${expandTabs$1(line.content)}`), indent, wrapWidth)), ...addedLines.flatMap((line) => renderWrappedLine(ui.ok(`+${expandTabs$1(line.content)}`), indent, wrapWidth))];
7608
+ }
7609
+ function renderIntraLineDiff(oldContent, newContent) {
7610
+ const parts = diffWords(oldContent, newContent);
7611
+ let removed = "";
7612
+ let added = "";
7613
+ for (const part of parts) if (part.removed) removed += highlightChangedPart(part.value);
7614
+ else if (part.added) added += highlightChangedPart(part.value);
7615
+ else {
7616
+ removed += part.value;
7617
+ added += part.value;
7618
+ }
7619
+ return {
7620
+ removed,
7621
+ added
7622
+ };
7623
+ }
7624
+ function highlightChangedPart(value) {
7625
+ const leading = value.match(/^\s*/u)?.[0] ?? "";
7626
+ const trailing = value.match(/\s*$/u)?.[0] ?? "";
7627
+ const body = value.slice(leading.length, value.length - trailing.length);
7628
+ return body.length === 0 ? value : `${leading}${ui.inverse(body)}${trailing}`;
7629
+ }
7630
+ function renderWrappedLine(line, indent, width) {
7631
+ return (width === void 0 ? [line] : wrapTextWithAnsi(line, width)).map((part) => `${indent}${part}`);
7632
+ }
7633
+ function isRemovedBodyLine(line) {
7634
+ return line.startsWith("-") && !line.startsWith("--- ");
7635
+ }
7636
+ function isAddedBodyLine(line) {
7637
+ return line.startsWith("+") && !line.startsWith("+++ ");
7638
+ }
7639
+ function expandTabs$1(text) {
7640
+ return text.replace(/\t/gu, " ");
7641
+ }
7642
+ //#endregion
7559
7643
  //#region src/tui/markdown.ts
7560
7644
  const codeFenceSentinel = "topchester-code-fence";
7561
7645
  function renderMarkdown(text, width) {
@@ -7647,16 +7731,13 @@ function thinkingMessage(text) {
7647
7731
  text
7648
7732
  };
7649
7733
  }
7650
- function toolCallMessage(call, label, resultSummary) {
7651
- return resultSummary === void 0 ? {
7652
- kind: "tool_call",
7653
- call,
7654
- label
7655
- } : {
7734
+ function toolCallMessage(call, label, resultSummary, diff) {
7735
+ return {
7656
7736
  kind: "tool_call",
7657
7737
  call,
7658
7738
  label,
7659
- resultSummary
7739
+ ...resultSummary === void 0 ? {} : { resultSummary },
7740
+ ...diff === void 0 ? {} : { diff }
7660
7741
  };
7661
7742
  }
7662
7743
  function hookStatusMessage(label, eventName, statusMessage) {
@@ -7682,7 +7763,7 @@ function modalMessage(message) {
7682
7763
  const DEFAULT_MODAL_VISIBLE_ACTION_LIMIT = 16;
7683
7764
  function renderChatMessage(message, options = {}) {
7684
7765
  if (message.kind === "modal") return renderChatModal(message, options.selectedActionIndex, options.maxModalHeight, options.width);
7685
- if (message.kind === "tool_call") return renderToolCallMessage(message);
7766
+ if (message.kind === "tool_call") return renderToolCallMessage(message, options.width);
7686
7767
  if (message.kind === "hook_status") return renderHookStatusMessage(message);
7687
7768
  if (message.kind === "subagent") return renderSubagentMessage(message);
7688
7769
  if (message.kind === "thinking") return message.text.split("\n").map((line) => ui.muted(line));
@@ -7714,9 +7795,14 @@ function renderSystemMessage(lines) {
7714
7795
  function formatSystemBodyLine(line) {
7715
7796
  return expandTabs(line).replace(/\(changed \+\d+\/-\d+\)$/u, (summary) => ui.muted(summary)).replace(/\(created \+\d+\)$/u, (summary) => ui.muted(summary));
7716
7797
  }
7717
- function renderToolCallMessage(message) {
7798
+ function renderToolCallMessage(message, width) {
7718
7799
  const visibleLabel = message.resultSummary && !message.label.includes(message.resultSummary) ? `${message.label} ${message.resultSummary}` : message.label;
7719
- return [` ${ui.muted(expandTabs(visibleLabel))}`];
7800
+ const label = ` ${ui.muted(expandTabs(visibleLabel))}`;
7801
+ if (!message.diff) return [label];
7802
+ return [label, ...renderUnifiedDiff(message.diff, {
7803
+ indent: " ",
7804
+ width
7805
+ })];
7720
7806
  }
7721
7807
  function renderHookStatusMessage(message) {
7722
7808
  return [` ${ui.muted(expandTabs(message.label))}`];
@@ -7888,7 +7974,8 @@ const messagePayloadSchema = z.object({
7888
7974
  const toolCallPayloadSchema = z.object({
7889
7975
  kind: z.literal("tool_call"),
7890
7976
  label: z.string(),
7891
- call: z.record(z.string(), jsonValueSchema)
7977
+ call: z.record(z.string(), jsonValueSchema),
7978
+ diff: z.string().optional()
7892
7979
  });
7893
7980
  const hookStatusPayloadSchema = z.object({
7894
7981
  kind: z.literal("hook_status"),
@@ -8133,7 +8220,7 @@ function rehydrateSession(events) {
8133
8220
  if (event.role === "user") visibleOnlyActionValues = /* @__PURE__ */ new Set();
8134
8221
  break;
8135
8222
  case "tool_call":
8136
- messages.push(toolCallMessage(event.call, event.label));
8223
+ messages.push(toolCallMessage(event.call, event.label, void 0, event.diff));
8137
8224
  break;
8138
8225
  case "hook_status":
8139
8226
  messages.push(hookStatusMessage(event.label, event.eventName, event.statusMessage));
@@ -8757,11 +8844,16 @@ const agentEvent = {
8757
8844
  meta
8758
8845
  };
8759
8846
  },
8760
- toolCall(call, label) {
8761
- return {
8847
+ toolCall(call, label, diff) {
8848
+ return diff === void 0 ? {
8762
8849
  type: "tool_call",
8763
8850
  call,
8764
8851
  label
8852
+ } : {
8853
+ type: "tool_call",
8854
+ call,
8855
+ label,
8856
+ diff
8765
8857
  };
8766
8858
  },
8767
8859
  hookStatus(eventName, statusMessage) {
@@ -10487,7 +10579,8 @@ function runtimeEventToSessionPayload(event) {
10487
10579
  case "tool_call": return {
10488
10580
  kind: "tool_call",
10489
10581
  label: event.label,
10490
- call: event.call
10582
+ call: event.call,
10583
+ ...event.diff === void 0 ? {} : { diff: event.diff }
10491
10584
  };
10492
10585
  case "hook_status": return {
10493
10586
  kind: "hook_status",
@@ -11380,7 +11473,7 @@ var TopchesterAgentRuntime = class TopchesterAgentRuntime {
11380
11473
  const call = taskCalls[index];
11381
11474
  const toolResult = taskResults[index];
11382
11475
  for (const event of createInstructionContextEventsFromToolResult(toolResult, persistedProjectInstructionKeys)) yield event;
11383
- yield agentEvent.toolCall(call, formatToolCallMessage(call, toolResult));
11476
+ yield agentEvent.toolCall(call, formatToolCallMessage(call, toolResult), getToolCallDisplayDiff(toolResult));
11384
11477
  const postHookRun = this.startPostToolUseHook(call, modelToolCalls[index]?.id, toolResult, session, abortSignal);
11385
11478
  for await (const event of postHookRun.statusEvents) yield event;
11386
11479
  const postHook = await postHookRun.result;
@@ -11442,7 +11535,7 @@ var TopchesterAgentRuntime = class TopchesterAgentRuntime {
11442
11535
  const call = parallelCalls[index];
11443
11536
  const toolResult = parallelResults[index];
11444
11537
  for (const event of createInstructionContextEventsFromToolResult(toolResult, persistedProjectInstructionKeys)) yield event;
11445
- yield agentEvent.toolCall(call, formatToolCallMessage(call, toolResult));
11538
+ yield agentEvent.toolCall(call, formatToolCallMessage(call, toolResult), getToolCallDisplayDiff(toolResult));
11446
11539
  const postHookRun = this.startPostToolUseHook(call, modelToolCalls[index]?.id, toolResult, session, abortSignal);
11447
11540
  for await (const event of postHookRun.statusEvents) yield event;
11448
11541
  const postHook = await postHookRun.result;
@@ -11505,7 +11598,7 @@ var TopchesterAgentRuntime = class TopchesterAgentRuntime {
11505
11598
  }
11506
11599
  }
11507
11600
  for (const event of createInstructionContextEventsFromToolResult(toolResult, persistedProjectInstructionKeys)) yield event;
11508
- yield agentEvent.toolCall(executableToolCall, formatToolCallMessage(executableToolCall, toolResult));
11601
+ yield agentEvent.toolCall(executableToolCall, formatToolCallMessage(executableToolCall, toolResult), getToolCallDisplayDiff(toolResult));
11509
11602
  if (!isToolErrorResult(toolResult) && toolResult.tool === "plan_todo") yield agentEvent.taskPlan(toolResult.plan);
11510
11603
  const postHookRun = this.startPostToolUseHook(executableToolCall, toolCall.id, toolResult, session, abortSignal);
11511
11604
  for await (const event of postHookRun.statusEvents) yield event;
@@ -11811,6 +11904,9 @@ function createToolErrorResult(tool, message) {
11811
11904
  warning: message
11812
11905
  };
11813
11906
  }
11907
+ function getToolCallDisplayDiff(result) {
11908
+ if (result.tool === "edit_file" && !isToolErrorResult(result) && "diff" in result) return result.diff;
11909
+ }
11814
11910
  function isL1ContextDisabledByEnv() {
11815
11911
  const value = process.env.TOPCHESTER_DISABLE_L1_CONTEXT?.trim().toLowerCase();
11816
11912
  return value === "1" || value === "true" || value === "yes" || value === "on";
@@ -11851,7 +11947,7 @@ function createInstructionContextEvents(sources, persistedKeys) {
11851
11947
  function renderRuntimeEvent(event) {
11852
11948
  switch (event.type) {
11853
11949
  case "message": return [event.role === "assistant" ? agentMessage(event.text, event.meta) : systemMessage(event.text)];
11854
- case "tool_call": return [toolCallMessage(event.call, event.label)];
11950
+ case "tool_call": return [toolCallMessage(event.call, event.label, void 0, event.diff)];
11855
11951
  case "hook_status": return [hookStatusMessage(event.label, event.eventName, event.statusMessage)];
11856
11952
  case "knowledge_status": return [systemMessage([`KB status: ${formatKnowledgePathStatus(event.status)}${formatKbPathSource(event.status)}`, event.guidance].filter(Boolean).join("\n"))];
11857
11953
  case "choice": return [modalMessage({
@@ -12111,7 +12207,8 @@ function chatMessageToSessionPayload(message) {
12111
12207
  if (message.kind === "tool_call") return {
12112
12208
  kind: "tool_call",
12113
12209
  label: message.label,
12114
- call: message.call
12210
+ call: message.call,
12211
+ ...message.diff === void 0 ? {} : { diff: message.diff }
12115
12212
  };
12116
12213
  }
12117
12214
  function slashCommandToSessionPayload(command) {