@dreb/coding-agent 2.22.1 → 2.23.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.
@@ -28,6 +28,7 @@ import { getChangelogPath, getNewEntries, parseChangelog } from "../../utils/cha
28
28
  import { copyToClipboard } from "../../utils/clipboard.js";
29
29
  import { extensionForImageMimeType, readClipboardImage } from "../../utils/clipboard-image.js";
30
30
  import { parseGitUrl } from "../../utils/git.js";
31
+ import { extractCopyableText, getMessagePreview, getMessageRoleLabel } from "../../utils/message-text.js";
31
32
  import { ensureTool } from "../../utils/tools-manager.js";
32
33
  import { ArminComponent } from "./components/armin.js";
33
34
  import { AssistantMessageComponent } from "./components/assistant-message.js";
@@ -36,6 +37,7 @@ import { BorderedLoader } from "./components/bordered-loader.js";
36
37
  import { BranchSummaryMessageComponent } from "./components/branch-summary-message.js";
37
38
  import { BuddyComponent } from "./components/buddy-component.js";
38
39
  import { CompactionSummaryMessageComponent } from "./components/compaction-summary-message.js";
40
+ import { CopySelectorComponent } from "./components/copy-selector.js";
39
41
  import { CustomEditor } from "./components/custom-editor.js";
40
42
  import { CustomMessageComponent } from "./components/custom-message.js";
41
43
  import { DaxnutsComponent } from "./components/daxnuts.js";
@@ -1711,6 +1713,7 @@ export class InteractiveMode {
1711
1713
  this.defaultEditor.onAction("app.session.tree", () => this.showTreeSelector());
1712
1714
  this.defaultEditor.onAction("app.session.fork", () => this.showUserMessageSelector());
1713
1715
  this.defaultEditor.onAction("app.session.resume", () => this.showSessionSelector());
1716
+ this.defaultEditor.onAction("app.clipboard.copyMessages", () => this.showCopySelector());
1714
1717
  this.defaultEditor.onChange = (text) => {
1715
1718
  const wasBashMode = this.isBashMode;
1716
1719
  this.isBashMode = text.trimStart().startsWith("!");
@@ -3655,18 +3658,68 @@ export class InteractiveMode {
3655
3658
  }
3656
3659
  }
3657
3660
  async handleCopyCommand() {
3658
- const text = this.session.getLastAssistantText();
3659
- if (!text) {
3660
- this.showError("No agent messages to copy yet.");
3661
+ this.showCopySelector();
3662
+ }
3663
+ showCopySelector() {
3664
+ const messages = this.session.messages;
3665
+ if (messages.length === 0) {
3666
+ this.showStatus("No messages to copy");
3661
3667
  return;
3662
3668
  }
3663
- try {
3664
- await copyToClipboard(text);
3665
- this.showStatus("Copied last agent message to clipboard");
3666
- }
3667
- catch (error) {
3668
- this.showError(error instanceof Error ? error.message : String(error));
3669
+ // Build items from session messages
3670
+ const items = messages.map((msg, index) => ({
3671
+ index,
3672
+ roleLabel: getMessageRoleLabel(msg),
3673
+ preview: getMessagePreview(msg),
3674
+ }));
3675
+ // Hide buddy while selector is open to free vertical space
3676
+ const hadBuddy = this.buddyComponent !== null;
3677
+ if (hadBuddy) {
3678
+ this.widgetContainerBelow.clear();
3679
+ this.ui.requestRender();
3669
3680
  }
3681
+ // Calculate max visible items based on terminal height
3682
+ // Reserve lines for: header(4) + borders(2) + spacers(2) + scroll indicator(1) + footer(1) + padding(2) = ~12 lines overhead
3683
+ const terminalRows = this.ui.terminal.rows;
3684
+ const overhead = 12;
3685
+ const maxVisible = Math.max(3, Math.min(15, terminalRows - overhead));
3686
+ this.showSelector((done) => {
3687
+ const selector = new CopySelectorComponent(items, async (selectedIndices) => {
3688
+ done();
3689
+ // Restore buddy
3690
+ if (hadBuddy)
3691
+ this.renderWidgets();
3692
+ this.ui.requestRender();
3693
+ if (selectedIndices.length === 0) {
3694
+ this.showWarning("No messages selected");
3695
+ return;
3696
+ }
3697
+ // Extract text from selected messages in chronological order
3698
+ const selectedTexts = selectedIndices
3699
+ .map((i) => extractCopyableText(messages[i]))
3700
+ .filter((text) => text.length > 0);
3701
+ if (selectedTexts.length === 0) {
3702
+ this.showWarning("Selected messages have no copyable text");
3703
+ return;
3704
+ }
3705
+ const combined = selectedTexts.join("\n\n---\n\n");
3706
+ const result = await copyToClipboard(combined);
3707
+ const count = selectedTexts.length;
3708
+ if (result.method === "osc52") {
3709
+ this.showStatus(`Sent ${count} message${count === 1 ? "" : "s"} to terminal clipboard (OSC 52)`);
3710
+ }
3711
+ else {
3712
+ this.showStatus(`Copied ${count} message${count === 1 ? "" : "s"} to clipboard`);
3713
+ }
3714
+ }, () => {
3715
+ done();
3716
+ // Restore buddy
3717
+ if (hadBuddy)
3718
+ this.renderWidgets();
3719
+ this.ui.requestRender();
3720
+ }, maxVisible);
3721
+ return { component: selector, focus: selector.getMessageList() };
3722
+ });
3670
3723
  }
3671
3724
  handleNameCommand(text) {
3672
3725
  const name = text.replace(/^\/name\s*/, "").trim();