@meistrari/remy-cli 1.25.0-rc.3 → 1.25.0-rc.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.
Files changed (3) hide show
  1. package/README.md +3 -1
  2. package/dist/remy.js +282 -252
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -56,12 +56,14 @@ Press `n` in the dashboard. The new-session flow asks what you want done, select
56
56
 
57
57
  On the final review, press `c` to choose MCP servers. Arrows move, Space toggles optional servers, `s` searches, `r` refreshes, and Enter returns to review. Escape clears an active search before returning to review. Servers always included for CLI sessions are locked; unavailable optional connections cannot be added. Previously selected servers that disappear remain visible until you remove them. Catalog failures block creation: open the picker and press `r` to retry without losing your prompt.
58
58
 
59
- Use `Tab` while writing the request to complete a local file or directory path. Path suggestions use the same highlighted list as command autocomplete, with File/Directory labels and a menu sized to the terminal; arrows move, Enter accepts, Tab cycles, and Esc dismisses. Remy attaches selected paths when you confirm the request. You can also paste an absolute path directly; quote paths containing spaces (for example, `"/Users/me/Desktop/Screen Shot.png"`) or use shell-style backslash escapes. Autocomplete selection is not required. Each file may be at most 20 MiB. A directory may contain at most 20 MiB across its regular files before compression; Remy uploads it as a temporary `<directory>.tar.gz` archive with the selected directory preserved as its root, so the remote agent can extract and use its files. Remy rejects oversized attachments with guidance to choose a smaller file or directory, and rejects directory attachments that contain symbolic links or other non-regular entries. On macOS, `Ctrl+V` saves a clipboard image as a temporary PNG and adds its path to the request; Remy attaches it on confirmation. Remy uploads attachments before creating the session and shows their filenames beneath your message.
59
+ Use `Tab` while writing the request to complete a local file or directory path. Path suggestions use the same highlighted list as command autocomplete, with File/Directory labels and a menu sized to the terminal; arrows move, Enter accepts, Tab cycles, and Esc dismisses. Remy attaches selected paths when you confirm the request. You can also paste an absolute path directly; quote paths containing spaces (for example, `"/Users/me/Desktop/Screen Shot.png"`) or use shell-style backslash escapes. Autocomplete selection is not required. Each file may be at most 20 MiB. A directory may contain at most 20 MiB across its regular files before compression; Remy uploads it as a temporary `<directory>.tar.gz` archive with the selected directory preserved as its root, so the remote agent can extract and use its files. Prompt-path attachments are best-effort: missing, unreadable, oversized or unsupported paths and failed uploads are skipped, and the original message is sent with whichever attachments succeeded. This includes paths selected with Tab or inserted from the clipboard. A non-blocking notice names files skipped during preparation or upload, including selected files that disappeared. When creating a session, the notice carries into the conversation; unrecognized, unselected prose remains quiet. Slashes in ordinary prose do not make attachment errors block your message. Directory archives never follow symbolic links or include other non-regular entries. On macOS, `Ctrl+V` saves a clipboard image as a temporary PNG and adds its path to the request; Remy attaches it on confirmation. Remy uploads attachments before creating the session and shows their filenames beneath your message.
60
60
 
61
61
  In a session, `Esc` dismisses open command/path suggestions or help before it can stop an active turn. Long suggestion labels may be shortened on screen; accepting one inserts the complete value. `/changes` opens read-only workspace Changes; `/workspace` opens its repository Files tree. `/files` or `Ctrl+F` remains the published-file and attachment picker. In the workspace browser, arrows or `j`/`k` navigate, Enter opens a loaded entry (deleted Changes rows open their old-side diff), `s` searches loaded entries, and `r` refreshes. In a file or diff, `g` goes to a line, Space anchors a range, `a` stages captured lines for the next message, `o`/`n` chooses the old/new side, `e` expands verified full context, `w` changes diff scope, `v` switches file/diff, and `d` downloads current regular-file bytes to `~/Downloads`; this is a fresh read and may differ from the displayed capture, not a snapshot export. Esc unwinds search, selection, and navigation before returning with the composer draft intact; returning from a viewer restores the originating list row when it remains available. Up to five contexts can be staged; each range is limited to 200 lines and 12,000 characters. Stale or unavailable captures cannot be staged. Valid human-message workspace trailers display compact repository/path/line references by default; `Ctrl+O` expands the original text. `/context` reviews or removes staged references before sending. The full captured text remains in the canonical message, and malformed trailers display as ordinary text.
62
62
 
63
63
  Opening the workspace browser while a message is being submitted does not discard that attempt. When you return, a failed submission remains available with `Ctrl+R`; retry preserves its original text, captured context, attachments, settings, and request identity. This recovery lasts for the current attached session, not across CLI restarts.
64
64
 
65
+ The new-session request and conversation composers share path completion and attachment selection. `Tab` completes the path prefix immediately before the cursor, preserving the text after it; accepting a suggestion leaves the cursor at the inserted path, including with joined emoji, combining accents, and wide characters. Restored drafts, history, and search queries also place the cursor at the end of the actual text. Suggestions stay above the input on compact terminals. In the new-session wizard, long drafts scroll within the remaining input space so suggestions and both input dividers remain visible. Editing the draft dismisses the old suggestions, and `Esc` also cancels a lookup that is still loading. A lookup discarded after cursor movement does not consume the next `Esc`. Session commands and previous-message history are available in the conversation composer.
66
+
65
67
  ### Resume or follow up
66
68
 
67
69
  Every terminal follow-up is appended as an explicit **Steer**, so it targets the work currently in progress. Queuing a message for a later turn (**Next**) is Web-only for now. Remy can replay sessions containing queued-message dispatch, withdrawal, and failure events. Messages withdrawn or failed before reaching Remy show an activity notice; an active submission ends as cancelled or failed instead of waiting for a turn that never started.
package/dist/remy.js CHANGED
@@ -34592,7 +34592,7 @@ function formatSessionJson({
34592
34592
  `;
34593
34593
  }
34594
34594
  // src/version.ts
34595
- var remyCliVersion = "1.25.0-rc.3";
34595
+ var remyCliVersion = "1.25.0-rc.5";
34596
34596
 
34597
34597
  // src/self-update.ts
34598
34598
  import { spawn } from "child_process";
@@ -39610,8 +39610,8 @@ function completionMenuRows(terminalHeight) {
39610
39610
  }
39611
39611
 
39612
39612
  // src/tui/composer-height.ts
39613
- function resizeComposer(composer) {
39614
- composer.height = Math.min(Math.max(composer.editorView.getTotalVirtualLineCount(), 1), TUI_LAYOUT.composerMaxRows);
39613
+ function resizeComposer(composer, availableRows = TUI_LAYOUT.composerMaxRows) {
39614
+ composer.height = Math.min(Math.max(composer.editorView.getTotalVirtualLineCount(), 1), TUI_LAYOUT.composerMaxRows, Math.max(1, availableRows));
39615
39615
  }
39616
39616
 
39617
39617
  // src/tui/composer.ts
@@ -39688,6 +39688,10 @@ function createComposer({
39688
39688
  }
39689
39689
  };
39690
39690
  }
39691
+ function setComposerText(editor, text) {
39692
+ editor.setText(text);
39693
+ editor.gotoBufferEnd();
39694
+ }
39691
39695
 
39692
39696
  // src/tui/key-hints.ts
39693
39697
  import { dim as dim3, fg as fg4, StyledText as StyledText5 } from "@opentui/core";
@@ -40744,7 +40748,7 @@ async function createDashboardTui({
40744
40748
  render();
40745
40749
  }, openSessionSearch = function() {
40746
40750
  sessionSearchOpen = true;
40747
- sessionSearchComposer.textarea.setText(sessionSearchQuery);
40751
+ setComposerText(sessionSearchComposer.textarea, sessionSearchQuery);
40748
40752
  render();
40749
40753
  }, applySessionSearch = function() {
40750
40754
  if (!sessionSearchOpen)
@@ -41463,6 +41467,9 @@ function renderFeedback(feedback) {
41463
41467
  function attachmentUploadMessage(filenames) {
41464
41468
  return filenames.length === 1 ? `Attaching ${filenames[0]}\u2026` : `Attaching ${filenames.length} files\u2026`;
41465
41469
  }
41470
+ function attachmentSkippedMessage(filenames) {
41471
+ return `Continuing without attachments: ${filenames.map(terminalSafeText).join(", ")}.`;
41472
+ }
41466
41473
  function renderAttachmentFeedback(feedback) {
41467
41474
  return renderFeedback(feedback);
41468
41475
  }
@@ -41481,52 +41488,6 @@ function renderAttachmentSummary(attachments) {
41481
41488
  var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
41482
41489
  var SPINNER_INTERVAL_MS = 80;
41483
41490
 
41484
- // src/tui/completion-menu.ts
41485
- import { dim as dim5, fg as fg10, StyledText as StyledText9 } from "@opentui/core";
41486
- function renderCompletionMenu({ items, selectedIndex, hints, width, maxRows }) {
41487
- if (items.length === 0)
41488
- return;
41489
- const hint = renderKeyHints({ hints, width });
41490
- const hintRows = hint.chunks.map((chunk) => chunk.text).join("").split(`
41491
- `).length;
41492
- const optionBudget = Math.max(1, maxRows - hintRows);
41493
- const showCount = items.length > Math.min(TUI_LAYOUT.completionMaxOptions, optionBudget) && optionBudget > 1;
41494
- const maximumVisibleCompletions = Math.min(TUI_LAYOUT.completionMaxOptions, optionBudget - (showCount ? 1 : 0));
41495
- const firstIndex = Math.min(Math.max(0, selectedIndex - Math.floor(maximumVisibleCompletions / 2)), Math.max(0, items.length - maximumVisibleCompletions));
41496
- const visibleItems = items.slice(firstIndex, firstIndex + maximumVisibleCompletions);
41497
- const naturalLabelWidth = Math.max(...items.map((item) => Bun.stringWidth(item.label))) + 2;
41498
- const descriptionBudget = Math.max(0, width - 2 - Math.min(naturalLabelWidth, Math.floor(width * 0.6)));
41499
- const descriptionWidth = width >= 30 ? Math.min(descriptionBudget, Math.max(...items.map((item) => Bun.stringWidth(item.description)))) : 0;
41500
- const labelWidth = Math.max(0, Math.min(naturalLabelWidth, width - 2 - descriptionWidth));
41501
- const rows = visibleItems.map((item, index) => {
41502
- const selected = firstIndex + index === selectedIndex;
41503
- const chunks = [
41504
- fg10(PALETTE.remyAccent)(`${selected ? "\u203A " : " "}${padText(truncateText(item.label, labelWidth - 2), labelWidth)}`),
41505
- fg10(PALETTE.dimText)(truncateText(item.description, descriptionWidth))
41506
- ];
41507
- return new StyledText9(highlightSelection({ chunks, cursor: selected }));
41508
- });
41509
- if (showCount && items.length > visibleItems.length)
41510
- rows.push(new StyledText9([dim5(fg10(PALETTE.dimText)(`${firstIndex + 1}-${firstIndex + visibleItems.length} of ${items.length}`))]));
41511
- rows.push(hint);
41512
- return joinStyled(rows, `
41513
- `);
41514
- }
41515
-
41516
- // src/tui/path-completion-menu.ts
41517
- function renderPathCompletionMenu({ completions, selectedIndex, width, maxRows }) {
41518
- return renderCompletionMenu({
41519
- items: completions.map((completion) => ({
41520
- label: completion.display,
41521
- description: completion.display.endsWith("/") ? "Directory" : "File"
41522
- })),
41523
- selectedIndex,
41524
- hints: [{ key: "\u2191\u2193", action: "select" }, { key: "Enter", action: "accepts" }, { key: "Tab", action: "next" }, { key: "Esc", action: "dismiss" }],
41525
- width,
41526
- maxRows
41527
- });
41528
- }
41529
-
41530
41491
  // src/tui/path-completion.ts
41531
41492
  import { readdir } from "fs/promises";
41532
41493
  import { homedir } from "os";
@@ -41615,6 +41576,163 @@ function activePathToken(text) {
41615
41576
  return match?.[1];
41616
41577
  }
41617
41578
 
41579
+ // src/tui/completion-menu.ts
41580
+ import { dim as dim5, fg as fg10, StyledText as StyledText9 } from "@opentui/core";
41581
+ function renderCompletionMenu({ items, selectedIndex, hints, width, maxRows }) {
41582
+ if (items.length === 0)
41583
+ return;
41584
+ const hint = renderKeyHints({ hints, width });
41585
+ const hintRows = hint.chunks.map((chunk) => chunk.text).join("").split(`
41586
+ `).length;
41587
+ const optionBudget = Math.max(1, maxRows - hintRows);
41588
+ const showCount = items.length > Math.min(TUI_LAYOUT.completionMaxOptions, optionBudget) && optionBudget > 1;
41589
+ const maximumVisibleCompletions = Math.min(TUI_LAYOUT.completionMaxOptions, optionBudget - (showCount ? 1 : 0));
41590
+ const firstIndex = Math.min(Math.max(0, selectedIndex - Math.floor(maximumVisibleCompletions / 2)), Math.max(0, items.length - maximumVisibleCompletions));
41591
+ const visibleItems = items.slice(firstIndex, firstIndex + maximumVisibleCompletions);
41592
+ const naturalLabelWidth = Math.max(...items.map((item) => Bun.stringWidth(item.label))) + 2;
41593
+ const descriptionBudget = Math.max(0, width - 2 - Math.min(naturalLabelWidth, Math.floor(width * 0.6)));
41594
+ const descriptionWidth = width >= 30 ? Math.min(descriptionBudget, Math.max(...items.map((item) => Bun.stringWidth(item.description)))) : 0;
41595
+ const labelWidth = Math.max(0, Math.min(naturalLabelWidth, width - 2 - descriptionWidth));
41596
+ const rows = visibleItems.map((item, index) => {
41597
+ const selected = firstIndex + index === selectedIndex;
41598
+ const chunks = [
41599
+ fg10(PALETTE.remyAccent)(`${selected ? "\u203A " : " "}${padText(truncateText(item.label, labelWidth - 2), labelWidth)}`),
41600
+ fg10(PALETTE.dimText)(truncateText(item.description, descriptionWidth))
41601
+ ];
41602
+ return new StyledText9(highlightSelection({ chunks, cursor: selected }));
41603
+ });
41604
+ if (showCount && items.length > visibleItems.length)
41605
+ rows.push(new StyledText9([dim5(fg10(PALETTE.dimText)(`${firstIndex + 1}-${firstIndex + visibleItems.length} of ${items.length}`))]));
41606
+ rows.push(hint);
41607
+ return joinStyled(rows, `
41608
+ `);
41609
+ }
41610
+
41611
+ // src/tui/path-completion-menu.ts
41612
+ function renderPathCompletionMenu({ completions, selectedIndex, width, maxRows }) {
41613
+ return renderCompletionMenu({
41614
+ items: completions.map((completion) => ({
41615
+ label: completion.display,
41616
+ description: completion.display.endsWith("/") ? "Directory" : "File"
41617
+ })),
41618
+ selectedIndex,
41619
+ hints: [{ key: "\u2191\u2193", action: "select" }, { key: "Enter", action: "accepts" }, { key: "Tab", action: "next" }, { key: "Esc", action: "dismiss" }],
41620
+ width,
41621
+ maxRows
41622
+ });
41623
+ }
41624
+
41625
+ // src/tui/composer-paths.ts
41626
+ function createComposerPaths({
41627
+ editor,
41628
+ cwd,
41629
+ isActive,
41630
+ onChange
41631
+ }) {
41632
+ let previousText = editor.plainText;
41633
+ let selections = [];
41634
+ let completions = [];
41635
+ let selectedIndex = 0;
41636
+ let request;
41637
+ function dismiss() {
41638
+ const wasOpen = request !== undefined;
41639
+ request = undefined;
41640
+ completions = [];
41641
+ return wasOpen;
41642
+ }
41643
+ function matchesEditor(snapshot) {
41644
+ return isActive() && editor.plainText === snapshot.text && editor.cursorOffset === snapshot.cursor;
41645
+ }
41646
+ async function open5() {
41647
+ const snapshot = { text: editor.plainText, cursor: editor.cursorOffset };
41648
+ request = snapshot;
41649
+ const candidates = await completePathToken({ text: editor.editBuffer.getTextRange(0, snapshot.cursor), cwd });
41650
+ if (request !== snapshot)
41651
+ return;
41652
+ if (!matchesEditor(snapshot)) {
41653
+ dismiss();
41654
+ return;
41655
+ }
41656
+ completions = candidates;
41657
+ selectedIndex = 0;
41658
+ if (candidates.length === 0)
41659
+ request = undefined;
41660
+ onChange();
41661
+ }
41662
+ function trackPath({ path, token, end }) {
41663
+ selections.push(createSelectedPathCompletion({ attachmentPath: path, token, end }));
41664
+ previousText = editor.plainText;
41665
+ }
41666
+ return {
41667
+ dismiss,
41668
+ update() {
41669
+ selections = reconcileSelectedPathCompletions({ previousText, text: editor.plainText, selections });
41670
+ previousText = editor.plainText;
41671
+ dismiss();
41672
+ },
41673
+ clearSelections() {
41674
+ selections = [];
41675
+ },
41676
+ selectedPaths(text) {
41677
+ return selectedAttachmentPaths({ text, selections });
41678
+ },
41679
+ insertPath(path) {
41680
+ editor.insertText(path);
41681
+ trackPath({ path, token: path, end: editor.editBuffer.getTextRange(0, editor.cursorOffset).length });
41682
+ },
41683
+ renderMenu({ width, maxRows }) {
41684
+ if (!request || !matchesEditor(request) || completions.length === 0)
41685
+ return;
41686
+ return renderPathCompletionMenu({ completions, selectedIndex, width, maxRows });
41687
+ },
41688
+ handleKey(key) {
41689
+ if (!isActive())
41690
+ return false;
41691
+ if (key.name === "escape" && dismiss()) {
41692
+ key.preventDefault();
41693
+ onChange();
41694
+ return true;
41695
+ }
41696
+ if (request && !matchesEditor(request)) {
41697
+ dismiss();
41698
+ onChange();
41699
+ }
41700
+ if (completions.length > 0) {
41701
+ if (key.name === "up" || key.name === "down" || key.name === "tab") {
41702
+ key.preventDefault();
41703
+ selectedIndex = (selectedIndex + (key.name === "up" ? -1 : 1) + completions.length) % completions.length;
41704
+ onChange();
41705
+ return true;
41706
+ }
41707
+ if (key.name === "return" || key.name === "enter") {
41708
+ key.preventDefault();
41709
+ const completion = completions[selectedIndex];
41710
+ const cursor = editor.cursorOffset;
41711
+ const cursorIndex = editor.editBuffer.getTextRange(0, cursor).length;
41712
+ const prefix = replaceActivePathToken({ text: editor.plainText.slice(0, cursorIndex), replacement: completion.replacement });
41713
+ const text = prefix + editor.plainText.slice(cursorIndex);
41714
+ dismiss();
41715
+ editor.gotoBufferEnd();
41716
+ const suffixOffset = editor.cursorOffset - cursor;
41717
+ setComposerText(editor, text);
41718
+ editor.cursorOffset -= suffixOffset;
41719
+ if (completion.attachmentPath)
41720
+ trackPath({ path: completion.attachmentPath, token: completion.replacement, end: prefix.length });
41721
+ onChange();
41722
+ return true;
41723
+ }
41724
+ dismiss();
41725
+ onChange();
41726
+ }
41727
+ if (key.name !== "tab")
41728
+ return false;
41729
+ key.preventDefault();
41730
+ open5();
41731
+ return true;
41732
+ }
41733
+ };
41734
+ }
41735
+
41618
41736
  // src/tui/new-session-wizard.ts
41619
41737
  async function createNewSessionWizard({
41620
41738
  repositories,
@@ -41692,6 +41810,8 @@ async function createNewSessionWizard({
41692
41810
  \u283F \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588`))
41693
41811
  ]);
41694
41812
  }, render = function() {
41813
+ if (step !== "prompt")
41814
+ composerPaths.dismiss();
41695
41815
  const repositoryListVisible = step === "repositories" || step === "repositorySearch";
41696
41816
  const mcpListVisible = step === "mcps" || step === "mcpSearch";
41697
41817
  const selectionListVisible = repositoryListVisible || step === "branches" || mcpListVisible;
@@ -41720,6 +41840,15 @@ async function createNewSessionWizard({
41720
41840
  repositoryRows.content = "";
41721
41841
  repositoryDivider.content = "";
41722
41842
  repositoryFooter.content = "";
41843
+ repositoryDivider.height = selectionListVisible ? "auto" : 0;
41844
+ repositoryFooter.height = selectionListVisible ? "auto" : 0;
41845
+ const menu = composerPaths.renderMenu({ width: renderer.width - 2, maxRows: completionMenuRows(renderer.height) });
41846
+ const menuRows = menu ? menu.chunks.map((chunk) => chunk.text).join("").split(`
41847
+ `).length : 0;
41848
+ pathMenu.content = menu ?? "";
41849
+ pathMenu.height = menuRows;
41850
+ if (step === "prompt")
41851
+ resizeComposer(editor, renderer.height - menuRows - 2);
41723
41852
  const attachmentText = renderAttachmentSummary(attachments);
41724
41853
  const attachmentStatus = attachmentFeedback ? renderAttachmentFeedback(attachmentFeedback) : undefined;
41725
41854
  if (step === "prompt") {
@@ -41729,9 +41858,6 @@ async function createNewSessionWizard({
41729
41858
  ];
41730
41859
  if (attachmentText.chunks.length > 0)
41731
41860
  parts.push(attachmentText);
41732
- const completionMenu = pathCompletionMenuOpen ? renderPathCompletionMenu({ completions: pathCompletions, selectedIndex: completionIndex, width: renderer.width - 2, maxRows: completionMenuRows(renderer.height) }) : undefined;
41733
- if (completionMenu)
41734
- parts.push(completionMenu);
41735
41861
  if (status)
41736
41862
  parts.push(stringToStyledText3(status));
41737
41863
  if (attachmentStatus)
@@ -41977,7 +42103,7 @@ async function createNewSessionWizard({
41977
42103
  rejectDraft(error93);
41978
42104
  }, returnToPromptFromRepositoryLoading = function() {
41979
42105
  step = "prompt";
41980
- editor.setText(prompt);
42106
+ setComposerText(editor, prompt);
41981
42107
  render();
41982
42108
  }, requestRepositoryLoad = function({ retry }) {
41983
42109
  if (Array.isArray(repositories))
@@ -42013,6 +42139,10 @@ async function createNewSessionWizard({
42013
42139
  render();
42014
42140
  });
42015
42141
  }, goBack = function() {
42142
+ if (composerPaths.dismiss()) {
42143
+ render();
42144
+ return;
42145
+ }
42016
42146
  if (step === "mcpSearch") {
42017
42147
  step = "mcps";
42018
42148
  mcpQuery = "";
@@ -42027,11 +42157,6 @@ async function createNewSessionWizard({
42027
42157
  render();
42028
42158
  return;
42029
42159
  }
42030
- if (pathCompletionMenuOpen) {
42031
- pathCompletionMenuOpen = false;
42032
- render();
42033
- return;
42034
- }
42035
42160
  if (step === "repositorySearch") {
42036
42161
  searchSelections = undefined;
42037
42162
  repositoryQuery = "";
@@ -42072,47 +42197,8 @@ async function createNewSessionWizard({
42072
42197
  }
42073
42198
  finish(null);
42074
42199
  }, handleKey = function(key) {
42075
- if (step === "prompt" && pathCompletionMenuOpen) {
42076
- if (key.name === "up" || key.name === "down") {
42077
- key.preventDefault();
42078
- completionIndex = (completionIndex + (key.name === "up" ? -1 : 1) + pathCompletions.length) % pathCompletions.length;
42079
- render();
42080
- return;
42081
- }
42082
- if (key.name === "tab") {
42083
- key.preventDefault();
42084
- completionIndex = (completionIndex + 1) % pathCompletions.length;
42085
- render();
42086
- return;
42087
- }
42088
- if (key.name === "return" || key.name === "enter") {
42089
- key.preventDefault();
42090
- const completion = pathCompletions[completionIndex];
42091
- if (completion) {
42092
- const completedText = replaceActivePathToken({ text: editor.plainText, replacement: completion.replacement });
42093
- editor.setText(completedText);
42094
- editor.cursorOffset = completedText.length;
42095
- if (completion.attachmentPath) {
42096
- const selectedCompletion = createSelectedPathCompletion({
42097
- attachmentPath: completion.attachmentPath,
42098
- token: completion.replacement,
42099
- end: completedText.length
42100
- });
42101
- selectedPathCompletions = [...selectedPathCompletions, selectedCompletion];
42102
- }
42103
- previousPromptEditorText = completedText;
42104
- }
42105
- pathCompletions = [];
42106
- pathCompletionMenuOpen = false;
42107
- render();
42108
- return;
42109
- }
42110
- }
42111
- if (step === "prompt" && key.name === "tab") {
42112
- key.preventDefault();
42113
- openPathCompletionMenu();
42200
+ if (composerPaths.handleKey(key))
42114
42201
  return;
42115
- }
42116
42202
  if (key.ctrl && key.name === "c") {
42117
42203
  key.preventDefault();
42118
42204
  finish({ kind: "interrupt" });
@@ -42193,7 +42279,7 @@ async function createNewSessionWizard({
42193
42279
  key.preventDefault();
42194
42280
  invalidatePendingConfirmation();
42195
42281
  step = "prompt";
42196
- editor.setText(prompt);
42282
+ setComposerText(editor, prompt);
42197
42283
  render();
42198
42284
  return;
42199
42285
  }
@@ -42340,12 +42426,14 @@ async function createNewSessionWizard({
42340
42426
  repositoryScrollBox.visible = false;
42341
42427
  const repositoryDivider = new TextRenderable4(renderer, { content: "", flexShrink: 0 });
42342
42428
  const repositoryFooter = new TextRenderable4(renderer, { content: "", flexShrink: 0 });
42429
+ const pathMenu = new TextRenderable4(renderer, { content: "", flexShrink: 0 });
42343
42430
  contentScrollBox.add(content2);
42344
42431
  root.add(contentScrollBox);
42345
42432
  repositoryScrollBox.add(repositoryRows);
42346
42433
  root.add(repositoryScrollBox);
42347
42434
  root.add(repositoryDivider);
42348
42435
  root.add(repositoryFooter);
42436
+ root.add(pathMenu);
42349
42437
  renderer.root.add(root);
42350
42438
  let loadedRepositories = Array.isArray(repositories) ? repositories : [];
42351
42439
  let repositoryLoadState = Array.isArray(repositories) ? "ready" : "loading";
@@ -42363,11 +42451,6 @@ async function createNewSessionWizard({
42363
42451
  let recomputeInstruction = "";
42364
42452
  let suggestionSpinnerFrame = 0;
42365
42453
  let attachments = [...initialDraft?.attachments ?? []];
42366
- let previousPromptEditorText = "";
42367
- let pathCompletions = [];
42368
- let selectedPathCompletions = [];
42369
- let completionIndex = 0;
42370
- let pathCompletionMenuOpen = false;
42371
42454
  let repositoryHeaderHeight;
42372
42455
  let awaitingVimGoToTop = false;
42373
42456
  const manualSelections = new Map;
@@ -42405,6 +42488,7 @@ async function createNewSessionWizard({
42405
42488
  rejectDraft = reject;
42406
42489
  });
42407
42490
  let editor;
42491
+ let composerPaths;
42408
42492
  const composer = createComposer({
42409
42493
  renderer,
42410
42494
  parent: root,
@@ -42413,28 +42497,28 @@ async function createNewSessionWizard({
42413
42497
  submitEditor();
42414
42498
  },
42415
42499
  onContentChange: () => {
42416
- if (step === "prompt") {
42417
- selectedPathCompletions = reconcileSelectedPathCompletions({
42418
- previousText: previousPromptEditorText,
42419
- text: editor.plainText,
42420
- selections: selectedPathCompletions
42421
- });
42422
- previousPromptEditorText = editor.plainText;
42423
- }
42424
- pathCompletionMenuOpen = false;
42500
+ if (step === "prompt")
42501
+ composerPaths.update();
42502
+ else
42503
+ composerPaths.dismiss();
42425
42504
  if (step === "mcpSearch") {
42426
42505
  mcpQuery = editor.plainText;
42427
42506
  mcpIndex = 0;
42428
- render();
42429
42507
  }
42430
42508
  if (step === "repositorySearch") {
42431
42509
  repositoryQuery = editor.plainText;
42432
42510
  repositoryIndex = 0;
42433
- render();
42434
42511
  }
42512
+ render();
42435
42513
  }
42436
42514
  });
42437
42515
  editor = composer.textarea;
42516
+ composerPaths = createComposerPaths({
42517
+ editor,
42518
+ cwd: process.cwd(),
42519
+ isActive: () => !destroyed && !settled && step === "prompt",
42520
+ onChange: render
42521
+ });
42438
42522
  let composerMounted = false;
42439
42523
  for (const repository of initialDraft?.repositories ?? [])
42440
42524
  manualSelections.set(repository.id, true);
@@ -42521,18 +42605,6 @@ async function createNewSessionWizard({
42521
42605
  }
42522
42606
  render();
42523
42607
  }
42524
- async function openPathCompletionMenu() {
42525
- if (destroyed)
42526
- return;
42527
- const text = editor.plainText;
42528
- const completions = await completePathToken({ text, cwd: process.cwd() });
42529
- if (destroyed || editor.plainText !== text)
42530
- return;
42531
- pathCompletions = completions;
42532
- completionIndex = 0;
42533
- pathCompletionMenuOpen = completions.length > 0;
42534
- render();
42535
- }
42536
42608
  async function startRepositoryReview() {
42537
42609
  if (repositoryLoadState !== "ready") {
42538
42610
  step = "loadingRepositories";
@@ -42563,10 +42635,7 @@ async function createNewSessionWizard({
42563
42635
  const image = await readClipboardImage();
42564
42636
  const path = await savePastedImage(image);
42565
42637
  if (!destroyed) {
42566
- editor.insertText(path);
42567
- const selectedCompletion = createSelectedPathCompletion({ attachmentPath: path, token: path, end: editor.cursorOffset });
42568
- selectedPathCompletions = [...selectedPathCompletions, selectedCompletion];
42569
- previousPromptEditorText = editor.plainText;
42638
+ composerPaths.insertPath(path);
42570
42639
  attachmentFeedback = { kind: "success", message: "Clipboard image ready to attach." };
42571
42640
  }
42572
42641
  } catch (error93) {
@@ -42602,11 +42671,12 @@ async function createNewSessionWizard({
42602
42671
  }
42603
42672
  const repositoryBranchOverrides = branchOverridesForSelection();
42604
42673
  const confirmation = Symbol("confirmation");
42674
+ const skippedFilenames = [];
42605
42675
  const confirmedAttachments = [...attachments];
42606
42676
  const confirmedPrompt = prompt;
42607
42677
  const confirmedModel = model;
42608
42678
  const confirmedReasoningEffort = reasoningEffort;
42609
- const confirmedSelectedPaths = selectedAttachmentPaths({ text: confirmedPrompt, selections: selectedPathCompletions });
42679
+ const confirmedSelectedPaths = composerPaths.selectedPaths(confirmedPrompt);
42610
42680
  pendingConfirmation = confirmation;
42611
42681
  const ownsConfirmation = () => !destroyed && !settled && step === "confirmation" && pendingConfirmation === confirmation;
42612
42682
  if (attachPromptPaths) {
@@ -42616,6 +42686,10 @@ async function createNewSessionWizard({
42616
42686
  text: confirmedPrompt,
42617
42687
  excludedPaths: confirmedAttachments.flatMap((attachment) => attachment.sourcePath ? [attachment.sourcePath] : []),
42618
42688
  selectedPaths: confirmedSelectedPaths,
42689
+ onUploadSkipped: ({ filename }) => {
42690
+ if (ownsConfirmation())
42691
+ skippedFilenames.push(filename);
42692
+ },
42619
42693
  onUploadStart: ({ filenames }) => {
42620
42694
  if (!ownsConfirmation())
42621
42695
  return;
@@ -42627,7 +42701,7 @@ async function createNewSessionWizard({
42627
42701
  return;
42628
42702
  confirmedAttachments.push(...promptAttachments);
42629
42703
  attachments = confirmedAttachments;
42630
- selectedPathCompletions = [];
42704
+ composerPaths.clearSelections();
42631
42705
  attachmentFeedback = undefined;
42632
42706
  } catch (error93) {
42633
42707
  if (!ownsConfirmation())
@@ -42646,6 +42720,7 @@ async function createNewSessionWizard({
42646
42720
  repositories: selected,
42647
42721
  ...repositoryBranchOverrides.length > 0 ? { repositoryBranchOverrides } : {},
42648
42722
  attachments: confirmedAttachments,
42723
+ ...skippedFilenames.length > 0 ? { attachmentWarning: attachmentSkippedMessage(skippedFilenames) } : {},
42649
42724
  ...confirmedMcps.length > 0 ? { mcps: confirmedMcps } : {},
42650
42725
  model: confirmedModel,
42651
42726
  reasoningEffort: confirmedReasoningEffort
@@ -43101,6 +43176,7 @@ async function createSessionTui({
43101
43176
  attachPromptPaths,
43102
43177
  initialComposer,
43103
43178
  initialFeedback,
43179
+ initialAttachmentWarning,
43104
43180
  now: now3 = Date.now,
43105
43181
  createRenderer = createDefaultRenderer3
43106
43182
  }) {
@@ -43169,7 +43245,7 @@ async function createSessionTui({
43169
43245
  composerFeedback = undefined;
43170
43246
  slashCompletions = [];
43171
43247
  helpVisible = false;
43172
- pathCompletionMenuOpen = false;
43248
+ composerPaths.dismiss();
43173
43249
  render();
43174
43250
  }, moveAgentSettingsPicker = function(step) {
43175
43251
  if (!agentSettingsPicker)
@@ -43205,10 +43281,9 @@ async function createSessionTui({
43205
43281
  }
43206
43282
  promptHistoryIndex = direction === "up" ? Math.max(0, promptHistoryIndex - 1) : Math.min(promptHistory.length, promptHistoryIndex + 1);
43207
43283
  const text = promptHistoryIndex === promptHistory.length ? promptHistoryDraft : promptHistory[promptHistoryIndex];
43208
- selectedPathCompletions = [];
43284
+ composerPaths.clearSelections();
43209
43285
  historyReplacement = text;
43210
- composer.setText(text);
43211
- composer.cursorOffset = text.length;
43286
+ setComposerText(composer, text);
43212
43287
  const viewport = composer.editorView.getViewport();
43213
43288
  composer.editorView.setViewport(viewport.offsetX, 0, viewport.width, viewport.height);
43214
43289
  render();
@@ -43252,7 +43327,7 @@ async function createSessionTui({
43252
43327
  if (latestState.aggregateStatus === "open")
43253
43328
  composerLayout.render();
43254
43329
  const submissionStatus = renderComposerStatus({ admittedSubmissions, width: renderer.width - 2 });
43255
- const completionMenu = pathCompletionMenuOpen ? renderPathCompletionMenu({ completions: pathCompletions, selectedIndex: completionIndex, width: renderer.width - 2, maxRows: completionMenuRows(renderer.height) }) : undefined;
43330
+ const completionMenu = composerPaths.renderMenu({ width: renderer.width - 2, maxRows: completionMenuRows(renderer.height) });
43256
43331
  const commandMenu = renderSlashCommandMenu({ commands: slashCompletions, selectedIndex: slashCompletionIndex, width: renderer.width - 2, maxRows: completionMenuRows(renderer.height) });
43257
43332
  const agentSettingsMenu = agentSettingsPicker && latestState.aggregateStatus === "open" ? renderAgentSettingsPicker({ picker: agentSettingsPicker, width: renderer.width - 2, maxRows: completionMenuRows(renderer.height) }) : undefined;
43258
43333
  const composerStatus = [
@@ -43314,7 +43389,7 @@ ${segment.excerpt}`).join(`
43314
43389
  }, openLocalMenu = function(mode) {
43315
43390
  resourceMode = mode;
43316
43391
  localMenuIndex = 0;
43317
- pathCompletionMenuOpen = false;
43392
+ composerPaths.dismiss();
43318
43393
  slashCompletions = [];
43319
43394
  helpVisible = false;
43320
43395
  composer.blur();
@@ -43326,7 +43401,7 @@ ${segment.excerpt}`).join(`
43326
43401
  resourceMode = "mcps";
43327
43402
  mcpQuery = "";
43328
43403
  mcpSelectedId = mcps.getState().attachments[0]?.offering.id;
43329
- pathCompletionMenuOpen = false;
43404
+ composerPaths.dismiss();
43330
43405
  slashCompletions = [];
43331
43406
  helpVisible = false;
43332
43407
  agentSettingsPicker = undefined;
@@ -43347,7 +43422,7 @@ ${segment.excerpt}`).join(`
43347
43422
  resourceMode = mode;
43348
43423
  resourceKey = resourceChoices()[0]?.key;
43349
43424
  resourceFeedback = undefined;
43350
- pathCompletionMenuOpen = false;
43425
+ composerPaths.dismiss();
43351
43426
  slashCompletions = [];
43352
43427
  helpVisible = false;
43353
43428
  agentSettingsPicker = undefined;
@@ -43503,16 +43578,11 @@ ${segment.excerpt}`).join(`
43503
43578
  let settled = false;
43504
43579
  let activityExpanded = false;
43505
43580
  let composerDraft = initialComposer ?? { text: "", attachments: [] };
43506
- let previousComposerText = composerDraft.text;
43507
- let pathCompletions = [];
43508
- let selectedPathCompletions = [];
43509
- let completionIndex = 0;
43510
- let pathCompletionMenuOpen = false;
43511
43581
  let slashCompletions = [];
43512
43582
  let slashCompletionIndex = 0;
43513
43583
  let agentSettingsPicker;
43514
43584
  let composerFeedback = initialFeedback ? { kind: "info", message: initialFeedback } : undefined;
43515
- let attachmentFeedback;
43585
+ let attachmentFeedback = initialAttachmentWarning ? { kind: "attention", message: initialAttachmentWarning } : undefined;
43516
43586
  let promptHistoryIndex;
43517
43587
  let promptHistoryDraft = "";
43518
43588
  let historyReplacement;
@@ -43583,6 +43653,7 @@ ${segment.excerpt}`).join(`
43583
43653
  });
43584
43654
  const composerTag = () => latestState.sessionNumber !== undefined ? `Session #${latestState.sessionNumber}` : latestState.sessionId;
43585
43655
  let composer;
43656
+ let composerPaths;
43586
43657
  const composerLayout = createComposer({
43587
43658
  renderer,
43588
43659
  parent: root,
@@ -43592,23 +43663,23 @@ ${segment.excerpt}`).join(`
43592
43663
  submitComposer();
43593
43664
  },
43594
43665
  onContentChange: () => {
43595
- selectedPathCompletions = reconcileSelectedPathCompletions({
43596
- previousText: previousComposerText,
43597
- text: composer.plainText,
43598
- selections: selectedPathCompletions
43599
- });
43600
- previousComposerText = composer.plainText;
43666
+ composerPaths.update();
43601
43667
  if (historyReplacement !== composer.plainText)
43602
43668
  promptHistoryIndex = undefined;
43603
43669
  historyReplacement = undefined;
43604
43670
  refreshSlashCompletions();
43605
- pathCompletionMenuOpen = false;
43606
43671
  if (composer.plainText.length > 0)
43607
43672
  helpVisible = false;
43608
43673
  render();
43609
43674
  }
43610
43675
  });
43611
43676
  composer = composerLayout.textarea;
43677
+ composerPaths = createComposerPaths({
43678
+ editor: composer,
43679
+ cwd: process.cwd(),
43680
+ isActive: () => !destroyed && !settled && latestState.aggregateStatus === "open" && resourceMode === undefined,
43681
+ onChange: render
43682
+ });
43612
43683
  const idleComposerPlaceholder = composer.placeholder;
43613
43684
  composerLayout.mount();
43614
43685
  const terminalSessionBand = new TextRenderable5(renderer, { content: "", flexShrink: 0, visible: false });
@@ -43700,7 +43771,7 @@ ${segment.excerpt}`).join(`
43700
43771
  } else if (key.name === "s") {
43701
43772
  mcpSearch = true;
43702
43773
  mcpSearchComposer.mount();
43703
- mcpSearchComposer.textarea.setText(mcpQuery);
43774
+ setComposerText(mcpSearchComposer.textarea, mcpQuery);
43704
43775
  mcpSearchComposer.focus();
43705
43776
  } else if (key.name === "r") {
43706
43777
  mcps.refresh();
@@ -43773,9 +43844,8 @@ ${segment.excerpt}`).join(`
43773
43844
  finish({ kind: "sessions" });
43774
43845
  return;
43775
43846
  }
43776
- if (pathCompletionMenuOpen) {
43847
+ if (composerPaths.dismiss()) {
43777
43848
  key.preventDefault();
43778
- pathCompletionMenuOpen = false;
43779
43849
  render();
43780
43850
  return;
43781
43851
  }
@@ -43822,10 +43892,7 @@ ${segment.excerpt}`).join(`
43822
43892
  const path = await savePastedImage(image);
43823
43893
  if (destroyed)
43824
43894
  return;
43825
- composer.insertText(path);
43826
- const selectedCompletion = createSelectedPathCompletion({ attachmentPath: path, token: path, end: composer.cursorOffset });
43827
- selectedPathCompletions = [...selectedPathCompletions, selectedCompletion];
43828
- previousComposerText = composer.plainText;
43895
+ composerPaths.insertPath(path);
43829
43896
  attachmentFeedback = { kind: "success", message: "Clipboard image ready to attach." };
43830
43897
  } catch (error93) {
43831
43898
  if (!destroyed)
@@ -43858,10 +43925,8 @@ ${segment.excerpt}`).join(`
43858
43925
  if (slashCompletions.length > 0 && key.name === "tab") {
43859
43926
  key.preventDefault();
43860
43927
  const completion = slashCompletions[slashCompletionIndex];
43861
- if (completion) {
43862
- composer.setText(completion.value);
43863
- composer.cursorOffset = completion.value.length;
43864
- }
43928
+ if (completion)
43929
+ setComposerText(composer, completion.value);
43865
43930
  slashCompletions = [];
43866
43931
  render();
43867
43932
  return;
@@ -43869,55 +43934,14 @@ ${segment.excerpt}`).join(`
43869
43934
  if (slashCompletions.length > 0 && (key.name === "return" || key.name === "enter")) {
43870
43935
  key.preventDefault();
43871
43936
  const completion = slashCompletions[slashCompletionIndex];
43872
- if (completion) {
43873
- composer.setText(completion.value);
43874
- composer.cursorOffset = completion.value.length;
43875
- }
43937
+ if (completion)
43938
+ setComposerText(composer, completion.value);
43876
43939
  slashCompletions = [];
43877
43940
  render();
43878
43941
  return;
43879
43942
  }
43880
- if (pathCompletionMenuOpen) {
43881
- if (key.name === "up" || key.name === "down") {
43882
- key.preventDefault();
43883
- completionIndex = (completionIndex + (key.name === "up" ? -1 : 1) + pathCompletions.length) % pathCompletions.length;
43884
- render();
43885
- return;
43886
- }
43887
- if (key.name === "tab") {
43888
- key.preventDefault();
43889
- completionIndex = (completionIndex + 1) % pathCompletions.length;
43890
- render();
43891
- return;
43892
- }
43893
- if (key.name === "return" || key.name === "enter") {
43894
- key.preventDefault();
43895
- const completion = pathCompletions[completionIndex];
43896
- if (completion) {
43897
- const completedText = replaceActivePathToken({ text: composer.plainText, replacement: completion.replacement });
43898
- composer.setText(completedText);
43899
- composer.cursorOffset = completedText.length;
43900
- if (completion.attachmentPath) {
43901
- const selectedCompletion = createSelectedPathCompletion({
43902
- attachmentPath: completion.attachmentPath,
43903
- token: completion.replacement,
43904
- end: completedText.length
43905
- });
43906
- selectedPathCompletions = [...selectedPathCompletions, selectedCompletion];
43907
- }
43908
- previousComposerText = completedText;
43909
- }
43910
- pathCompletions = [];
43911
- pathCompletionMenuOpen = false;
43912
- render();
43913
- return;
43914
- }
43915
- }
43916
- if (key.name === "tab") {
43917
- key.preventDefault();
43918
- openPathCompletionMenu();
43943
+ if (composerPaths.handleKey(key))
43919
43944
  return;
43920
- }
43921
43945
  if ((key.name === "up" || key.name === "down") && composerAtVisualEdge(key.name)) {
43922
43946
  key.preventDefault();
43923
43947
  navigatePromptHistory(key.name);
@@ -43996,18 +44020,6 @@ ${segment.excerpt}`).join(`
43996
44020
  });
43997
44021
  };
43998
44022
  }
43999
- async function openPathCompletionMenu() {
44000
- if (destroyed)
44001
- return;
44002
- const text = composer.plainText;
44003
- const completions = await completePathToken({ text, cwd: process.cwd() });
44004
- if (destroyed || composer.plainText !== text)
44005
- return;
44006
- pathCompletions = completions;
44007
- completionIndex = 0;
44008
- pathCompletionMenuOpen = completions.length > 0;
44009
- render();
44010
- }
44011
44023
  async function submitComposer() {
44012
44024
  if (attachmentPreparationOwner)
44013
44025
  return;
@@ -44042,12 +44054,17 @@ ${segment.excerpt}`).join(`
44042
44054
  return;
44043
44055
  }
44044
44056
  if (attachPromptPaths) {
44057
+ const skippedFilenames = [];
44045
44058
  let handedOffToAdmission = false;
44046
44059
  try {
44047
44060
  const attachments = await attachPromptPaths({
44048
44061
  text: composerDraft.text,
44049
44062
  excludedPaths: composerDraft.attachments.flatMap((attachment) => attachment.sourcePath ? [attachment.sourcePath] : []),
44050
- selectedPaths: selectedAttachmentPaths({ text: composerDraft.text, selections: selectedPathCompletions }),
44063
+ selectedPaths: composerPaths.selectedPaths(composerDraft.text),
44064
+ onUploadSkipped: ({ filename }) => {
44065
+ if (ownsAttachmentPreparation(preparationOwner))
44066
+ skippedFilenames.push(filename);
44067
+ },
44051
44068
  onUploadStart: ({ filenames }) => {
44052
44069
  if (!ownsAttachmentPreparation(preparationOwner))
44053
44070
  return;
@@ -44058,8 +44075,8 @@ ${segment.excerpt}`).join(`
44058
44075
  if (!ownsAttachmentPreparation(preparationOwner))
44059
44076
  return;
44060
44077
  composerDraft = { ...composerDraft, attachments: [...composerDraft.attachments, ...attachments] };
44061
- selectedPathCompletions = [];
44062
- attachmentFeedback = undefined;
44078
+ composerPaths.clearSelections();
44079
+ attachmentFeedback = skippedFilenames.length > 0 ? { kind: "attention", message: attachmentSkippedMessage(skippedFilenames) } : undefined;
44063
44080
  handedOffToAdmission = true;
44064
44081
  } catch (error93) {
44065
44082
  if (!ownsAttachmentPreparation(preparationOwner))
@@ -44184,7 +44201,6 @@ ${segment.excerpt}`).join(`
44184
44201
  helpVisible = true;
44185
44202
  slashCompletions = composerSlashCommands;
44186
44203
  slashCompletionIndex = 0;
44187
- pathCompletionMenuOpen = false;
44188
44204
  render();
44189
44205
  return true;
44190
44206
  }
@@ -44337,7 +44353,7 @@ ${segment.excerpt}`).join(`
44337
44353
  });
44338
44354
  renderer.addInputHandler(inputHandler);
44339
44355
  renderer.keyInput.on("keypress", keyHandler);
44340
- composer.setText(composerDraft.text);
44356
+ setComposerText(composer, composerDraft.text);
44341
44357
  renderer.on(CliRenderEvents5.RENDER_ERROR, (event) => finish(undefined, event.error));
44342
44358
  resizeHandler = () => {
44343
44359
  if (!destroyed)
@@ -44989,26 +45005,28 @@ async function attachPromptPaths({
44989
45005
  excludedPaths = [],
44990
45006
  selectedPaths = [],
44991
45007
  reserveAndUploadFile: reserveAndUploadFile2,
44992
- onUploadStart
45008
+ onUploadStart,
45009
+ onUploadSkipped
44993
45010
  }) {
45011
+ const selected = new Set(selectedPaths.map((filePath) => resolve3(cwd, filePath)));
44994
45012
  const excluded = new Set(excludedPaths);
44995
45013
  const pathsToUpload = [];
44996
- for (const candidate of promptFilePaths({ text, cwd, selectedPaths })) {
44997
- const { filePath } = candidate;
45014
+ for (const filePath of promptFilePaths({ text, cwd, selectedPaths })) {
44998
45015
  if (excluded.has(filePath))
44999
45016
  continue;
45000
45017
  let file3;
45001
45018
  try {
45002
45019
  file3 = await stat(filePath);
45003
- } catch (error93) {
45004
- if (error93 instanceof Error && "code" in error93 && error93.code === "ENOENT")
45005
- continue;
45006
- throw error93;
45020
+ } catch {
45021
+ if (selected.has(filePath))
45022
+ onUploadSkipped?.({ filename: basename6(filePath) });
45023
+ continue;
45007
45024
  }
45008
- if (!file3.isFile() && !file3.isDirectory() && candidate.explicit)
45009
- throw new Error(`Prompt attachment path is not a regular file or directory: ${filePath}`);
45010
- if (!file3.isFile() && !file3.isDirectory())
45025
+ if (!file3.isFile() && !file3.isDirectory()) {
45026
+ if (selected.has(filePath))
45027
+ onUploadSkipped?.({ filename: basename6(filePath) });
45011
45028
  continue;
45029
+ }
45012
45030
  pathsToUpload.push(filePath);
45013
45031
  }
45014
45032
  if (pathsToUpload.length === 0)
@@ -45016,11 +45034,16 @@ async function attachPromptPaths({
45016
45034
  onUploadStart?.({ filenames: pathsToUpload.map((filePath) => basename6(filePath)) });
45017
45035
  const attachments = [];
45018
45036
  for (const filePath of pathsToUpload) {
45019
- const uploaded = await uploadLocalAttachment({ filePath, reserveAndUploadFile: reserveAndUploadFile2 });
45020
- attachments.push({
45021
- id: randomUUID5(),
45022
- ...uploaded
45023
- });
45037
+ try {
45038
+ const uploaded = await uploadLocalAttachment({ filePath, reserveAndUploadFile: reserveAndUploadFile2 });
45039
+ attachments.push({
45040
+ id: randomUUID5(),
45041
+ ...uploaded
45042
+ });
45043
+ } catch {
45044
+ onUploadSkipped?.({ filename: basename6(filePath) });
45045
+ continue;
45046
+ }
45024
45047
  }
45025
45048
  return attachments;
45026
45049
  }
@@ -45155,9 +45178,9 @@ function truncateUtf8(value, maxBytes) {
45155
45178
  return result;
45156
45179
  }
45157
45180
  function promptFilePaths({ text, cwd, selectedPaths }) {
45158
- const paths = new Map;
45181
+ const paths = new Set;
45159
45182
  for (const selectedPath of selectedPaths)
45160
- paths.set(isAbsolute3(selectedPath) ? selectedPath : resolve3(cwd, selectedPath), true);
45183
+ paths.add(isAbsolute3(selectedPath) ? selectedPath : resolve3(cwd, selectedPath));
45161
45184
  const tokens = text.match(/[([{]*@?(?:"[^"]*"|'[^']*'|`[^`]*`)|(?:\\.|[^\s\\])+/g) ?? [];
45162
45185
  for (const quotedToken of tokens) {
45163
45186
  const wrappedToken = quotedToken.replace(/^[([{]+/, "").replace(/[),.;:!?\]}]+$/, "");
@@ -45172,9 +45195,9 @@ function promptFilePaths({ text, cwd, selectedPaths }) {
45172
45195
  continue;
45173
45196
  const expandedPath = pathText.startsWith("~/") ? join9(homedir2(), pathText.slice(2)) : pathText;
45174
45197
  const filePath = isAbsolute3(expandedPath) ? expandedPath : resolve3(cwd, expandedPath);
45175
- paths.set(filePath, paths.get(filePath) || explicit);
45198
+ paths.add(filePath);
45176
45199
  }
45177
- return [...paths].map(([filePath, explicit]) => ({ filePath, explicit }));
45200
+ return [...paths];
45178
45201
  }
45179
45202
  function extensionForMediaType(mediaType) {
45180
45203
  if (mediaType === "image/png")
@@ -46177,7 +46200,7 @@ async function dashboard({
46177
46200
  holdTransitionScreen();
46178
46201
  continue;
46179
46202
  }
46180
- return await attachCreatedSession({ dependencies, operations, created: result.created, repositories: result.repositories, noTui: false, json: false });
46203
+ return await attachCreatedSession({ dependencies, operations, created: result.created, repositories: result.repositories, attachmentWarning: result.attachmentWarning, noTui: false, json: false });
46181
46204
  }
46182
46205
  }
46183
46206
  async function prepareDashboardBootstrap({
@@ -46421,7 +46444,8 @@ async function attachCreatedSession({
46421
46444
  created,
46422
46445
  repositories,
46423
46446
  noTui,
46424
- json: json3
46447
+ json: json3,
46448
+ attachmentWarning
46425
46449
  }) {
46426
46450
  const initialMessage = created.messages[0];
46427
46451
  if (!initialMessage)
@@ -46449,7 +46473,8 @@ async function attachCreatedSession({
46449
46473
  },
46450
46474
  noTui,
46451
46475
  json: json3,
46452
- emitCreated: true
46476
+ emitCreated: true,
46477
+ attachmentWarning
46453
46478
  });
46454
46479
  }
46455
46480
  async function createFromTuiDraft({
@@ -46494,7 +46519,7 @@ async function createReviewedSession({ dependencies, operations, repositories })
46494
46519
  showRemyTransitionScreen({ write: (chunk) => dependencies.output.writeStdout(chunk) });
46495
46520
  try {
46496
46521
  const created = await createFromTuiDraft({ dependencies, operations, draft });
46497
- return { created, repositories: draft.repositories };
46522
+ return { created, repositories: draft.repositories, attachmentWarning: draft.attachmentWarning };
46498
46523
  } catch (error93) {
46499
46524
  throwIfAborted2(dependencies.abortSignal);
46500
46525
  initialDraft = draft;
@@ -46532,13 +46557,15 @@ async function uploadPromptPaths({
46532
46557
  text,
46533
46558
  excludedPaths,
46534
46559
  selectedPaths,
46535
- onUploadStart
46560
+ onUploadStart,
46561
+ onUploadSkipped
46536
46562
  }) {
46537
46563
  return await attachPromptPaths({
46538
46564
  text,
46539
46565
  excludedPaths,
46540
46566
  selectedPaths,
46541
46567
  onUploadStart,
46568
+ onUploadSkipped,
46542
46569
  cwd: process.cwd(),
46543
46570
  reserveAndUploadFile: async ({ filePath, mediaType }) => await operations.reserveAndUploadFile({
46544
46571
  client: operations.client,
@@ -46634,7 +46661,8 @@ async function runAttachedSession({
46634
46661
  noTui,
46635
46662
  json: json3,
46636
46663
  emitCreated,
46637
- onCacheError
46664
+ onCacheError,
46665
+ attachmentWarning
46638
46666
  }) {
46639
46667
  if (dependencies.abortSignal?.aborted)
46640
46668
  throw dependencies.abortSignal.reason ?? new Error("interrupted");
@@ -46722,6 +46750,7 @@ async function runAttachedSession({
46722
46750
  fetchClient: globalThis.fetch
46723
46751
  }),
46724
46752
  initialComposer: composerSnapshot,
46753
+ initialAttachmentWarning: attachmentWarning,
46725
46754
  admissions,
46726
46755
  repositoryLabel: repositories.map((repository) => repository.fullName).join(", ") || undefined,
46727
46756
  submitMessage: async (submission) => {
@@ -46871,6 +46900,7 @@ async function runAttachedSession({
46871
46900
  operations,
46872
46901
  created: result.created,
46873
46902
  repositories: result.repositories,
46903
+ attachmentWarning: result.attachmentWarning,
46874
46904
  noTui: false,
46875
46905
  json: false
46876
46906
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@meistrari/remy-cli",
3
- "version": "1.25.0-rc.3",
3
+ "version": "1.25.0-rc.5",
4
4
  "description": "Remy, the Coding Agent terminal client.",
5
5
  "type": "module",
6
6
  "bin": {