nou-cli 0.5.13 → 0.5.14

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/README.md CHANGED
@@ -113,7 +113,7 @@ Every change is reviewed like a code review:
113
113
  | <kbd>m</kbd> | mouse on/off, so you can select and copy text in the terminal (in the chat, just drag over the text: it's copied when you let go; double-click copies a word, path or link, triple-click a paragraph) |
114
114
  | <kbd>s</kbd> | settings: model, preferences, plugins |
115
115
  | <kbd>y</kbd> / <kbd>n</kbd> | accept / reject a proposed edit or command, from any pane; the rest of Nou keeps working while it waits (<kbd>o</kbd> shows the edit) |
116
- | <kbd>Enter</kbd> while Nou works | queue the message: it goes when Nou finishes, together with anything else you queued, as one message; commands that only change the screen, like `/terminal`, `/games` or `/open`, run right away. To send now instead of waiting: <kbd>Alt</kbd>+<kbd>Enter</kbd> (stops Nou and sends it), click the queued message, or `/now`; or turn on **Send while Nou works** in settings so <kbd>Enter</kbd> sends right away (<kbd>Alt</kbd>+<kbd>Enter</kbd> then queues) |
116
+ | <kbd>Enter</kbd> while Nou works | queue the message: it goes when Nou finishes, together with anything else you queued, as one message; commands that only change the screen, like `/terminal`, `/games` or `/open`, run right away. To send now instead of waiting: <kbd>Alt</kbd>+<kbd>Enter</kbd> (stops Nou and sends it), click the queued message, or `/now`. Nou answers it first, then finishes what it was working on, unless your message says to drop it; or turn on **Send while Nou works** in settings so <kbd>Enter</kbd> sends right away (<kbd>Alt</kbd>+<kbd>Enter</kbd> then queues) |
117
117
  | <kbd>Esc</kbd> | stop Nou (anything queued goes back in the input) · close a diff · back to chat |
118
118
  | <kbd>Ctrl</kbd>+<kbd>C</kbd> | stop Nou while it works · press twice to quit |
119
119
 
@@ -35,7 +35,7 @@ import { FIND_KEY, isFind } from "./keys.js";
35
35
  import { FileTree } from "./FileTree.js";
36
36
  import { CodeView } from "./CodeView.js";
37
37
  import { Editor } from "./Editor.js";
38
- import { SLASH_COMMANDS, closestCommand, onlyChangesScreen, runsWhileBusy } from "./commands.js";
38
+ import { SLASH_COMMANDS, closestCommand, finishAfter, onlyChangesScreen, runsWhileBusy } from "./commands.js";
39
39
  import { WHEEL_STEP, sendWheel } from "./scroll.js";
40
40
  import { Chat, chatTabZones } from "./Chat.js";
41
41
  import { copyToClipboard, openInBrowser } from "./clipboard.js";
@@ -2017,6 +2017,17 @@ ${snippet}
2017
2017
  agentRef.current?.cancel();
2018
2018
  stopped.current = true;
2019
2019
  };
2020
+ /** What Nou was working on when a message cut in (see interruptToSend), so it finishes that too. */
2021
+ const unfinished = useRef(null);
2022
+ const lastAsked = useRef(null);
2023
+ /** Stops Nou so what's queued goes now; the request it was on isn't dropped, it's handed back with it. */
2024
+ const interruptToSend = () => {
2025
+ // What the agent was last asked, unfinished work handed back included: cutting in twice keeps both.
2026
+ if (lastAsked.current)
2027
+ unfinished.current = lastAsked.current;
2028
+ stop();
2029
+ stopped.current = false; // what's queued is meant to go
2030
+ };
2020
2031
  useEffect(() => {
2021
2032
  if (busy || approval || !queued.length)
2022
2033
  return;
@@ -2031,7 +2042,10 @@ ${snippet}
2031
2042
  const isCommand = (text) => text.startsWith("/");
2032
2043
  const take = isCommand(queued[0]) ? 1 : Math.max(1, queued.findIndex(isCommand) === -1 ? queued.length : queued.findIndex(isCommand));
2033
2044
  setQueued(queued.slice(take));
2034
- send(queued.slice(0, take).join("\n\n"));
2045
+ const text = queued.slice(0, take).join("\n\n");
2046
+ const previous = unfinished.current;
2047
+ unfinished.current = null;
2048
+ send(text, previous && !isCommand(text) ? finishAfter(text, previous) : undefined);
2035
2049
  }, [busy, approval, queued]);
2036
2050
  // When a reply points at places in the code (Nou's engine with show_places, the others with a ```nou-show
2037
2051
  // block, which is taken out of the reply), the first one opens highlighted and the rest are listed.
@@ -2186,8 +2200,7 @@ ${snippet}
2186
2200
  const sendNow = () => {
2187
2201
  if (!queued.length)
2188
2202
  return;
2189
- stop();
2190
- stopped.current = false; // what's queued is meant to go
2203
+ interruptToSend();
2191
2204
  };
2192
2205
  /**
2193
2206
  * From the chat: while Nou works, a message waits its turn, or (`now`: Alt+Enter) stops Nou and goes right
@@ -2198,14 +2211,14 @@ ${snippet}
2198
2211
  return send(text);
2199
2212
  const now = settings.sendNowWhileBusy ? !alt : alt;
2200
2213
  if (now) {
2201
- stop();
2202
- stopped.current = false; // this one is meant to go
2214
+ interruptToSend();
2203
2215
  setQueued((q) => [text, ...q]);
2204
2216
  }
2205
2217
  else
2206
2218
  setQueued((q) => [...q, text]);
2207
2219
  };
2208
- const send = (text) => {
2220
+ /** `forAgent`: what the agent gets instead of `text` (the chat still shows `text`). */
2221
+ const send = (text, forAgent) => {
2209
2222
  // Right after /resume lists them, a number picks a conversation instead of being sent to the agent.
2210
2223
  const picking = pickingResume.current;
2211
2224
  pickingResume.current = false;
@@ -2222,7 +2235,8 @@ ${snippet}
2222
2235
  return;
2223
2236
  }
2224
2237
  setEntries((prev) => [...prev, { kind: "user", text }]);
2225
- void agentRef.current.send(text, editorContext() ?? undefined);
2238
+ lastAsked.current = forAgent ?? text;
2239
+ void agentRef.current.send(forAgent ?? text, editorContext() ?? undefined);
2226
2240
  };
2227
2241
  function saveFromEditor(text, file = openFile) {
2228
2242
  if (!file)
@@ -96,3 +96,11 @@ export function matchCommands(typed, extra = []) {
96
96
  const contains = query ? all.filter((c) => !c.name.startsWith(query) && (c.name.includes(query) || c.description.includes(query))) : [];
97
97
  return [...starts.sort((a, b) => Number(b.name === query) - Number(a.name === query)), ...contains];
98
98
  }
99
+ /**
100
+ * A message sent while Nou was working stops it so it's answered now, but what Nou was on isn't dropped:
101
+ * the agent is told what that was and to finish it after, unless the new message says otherwise.
102
+ */
103
+ export function finishAfter(text, previous) {
104
+ const was = previous.length > 6000 ? `${previous.slice(0, 6000)}…` : previous;
105
+ return `${text}\n\n[Sent while you were still working on my previous message, so you were interrupted. Handle this first, then carry on with and finish the previous request, picking up where you left off (don't redo finished steps), unless this message cancels or replaces it. The previous request was:\n${was}]`;
106
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nou-cli",
3
- "version": "0.5.13",
3
+ "version": "0.5.14",
4
4
  "description": "Nou: an IDE-style AI coding assistant for your terminal. Local (Ollama, LM Studio) and cloud (Claude, OpenAI, Gemini...) models, live diffs, git-aware, built-in editor.",
5
5
  "type": "module",
6
6
  "bin": {