ucode-agent 1.14.0 → 1.15.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ucode-agent",
3
- "version": "1.14.0",
3
+ "version": "1.15.0",
4
4
  "description": "ucode - a terminal coding agent that reads, edits and runs your code, on NVIDIA and Cohere models.",
5
5
  "type": "module",
6
6
  "main": "ucode.js",
package/src/core/loop.js CHANGED
@@ -434,6 +434,23 @@ function systemPrompt({ cwd, skills, mode, check, map, memory }) {
434
434
  '',
435
435
  '## How to work',
436
436
  '',
437
+ 'Before building anything, turn the request into a list of what it must do — every',
438
+ 'feature named, and the ones any user would expect whether or not they were named',
439
+ '(an empty state, an error state, the keyboard doing the obvious thing, working on',
440
+ 'a phone). Keep it as the plan. Build against it, then go through it one item at a',
441
+ 'time before you say a word about being finished. Most of what gets missed was',
442
+ 'never written down.',
443
+ '',
444
+ 'Do not put code in your reply. Not a snippet, not "here is the key part", not a',
445
+ 'summary of the file. It is already in the file and the user can open it; pasting',
446
+ 'it again buries the one or two sentences that actually matter. Say what it does',
447
+ 'and what to try.',
448
+ '',
449
+ 'Do not claim it is done while anything is still running or unchecked. "I have',
450
+ 'built it" said before the build finishes is worse than saying nothing: the user',
451
+ 'believes you, looks, and finds it broken. Finish, check, then say so — and if',
452
+ 'something is incomplete, say which part and why.',
453
+ '',
437
454
  'FIRST, EVERY TIME: one short line saying what you are about to do, then the tool',
438
455
  'calls. Never open a turn with a tool call and no words. "Right, the HTML',
439
456
  'structure first." / "Now the state and the render loop." / "That is the layout',
@@ -986,7 +1003,7 @@ export class Agent {
986
1003
  };
987
1004
  // Only a real terminal has somewhere to stream into.
988
1005
  if (this.full) {
989
- opts.onThinking = () => this.ui.thinkingDelta();
1006
+ opts.onThinking = (delta) => this.ui.thinkingDelta(delta);
990
1007
  opts.onText = (delta) => {
991
1008
  if (!streaming) {
992
1009
  streaming = true;
package/src/ui/screen.js CHANGED
@@ -532,17 +532,54 @@ export class Screen {
532
532
  // than silence. The spinner counts the seconds so the wait is visibly alive,
533
533
  // and the transcript gets one line afterwards saying how long it took.
534
534
 
535
- thinkingDelta() {
535
+ /**
536
+ * The model's reasoning, live, one line at a time.
537
+ *
538
+ * Models reach for a tool before they say anything, so the first words of a
539
+ * step were arriving a minute after it began — while the reasoning channel
540
+ * had been streaming words the whole time and we were throwing them away to
541
+ * keep a timer. Its latest sentence now shows on one line that rewrites
542
+ * itself, which is something to read from the first second.
543
+ *
544
+ * It is scaffolding, not the answer: it shimmers while it is live and it is
545
+ * taken off the screen the moment the real reply starts.
546
+ */
547
+ thinkingDelta(text = '') {
536
548
  if (this.thoughtSince === undefined) this.thoughtSince = Date.now();
549
+ if (!text) return;
550
+
551
+ this.thought = ((this.thought ?? '') + text).slice(-2000);
552
+ // The last sentence it has finished, or what it has written of the next.
553
+ const parts = this.thought.split(/(?<=[.!?])\s+/).filter((p) => p.trim());
554
+ const latest = (parts[parts.length - 1] ?? '').replace(/\s+/g, ' ').trim();
555
+ if (!latest) return;
556
+
557
+ const line = ` ${shimmer(clip(latest, Math.max(20, this.width() - 6)), this.tick * FRAME_MS)}`;
558
+ if (this.thinkAt === undefined || this.lines[this.thinkAt] === undefined) {
559
+ this.thinkAt = this.lines.length;
560
+ this.push(line);
561
+ } else {
562
+ this.lines[this.thinkAt] = line;
563
+ this.render();
564
+ }
565
+ }
566
+
567
+ /** Keep the live thought moving between deltas. */
568
+ paintLiveThought() {
569
+ if (this.thinkAt !== undefined && this.lines[this.thinkAt] !== undefined) this.thinkingDelta('');
537
570
  }
538
571
 
539
572
  thinkingEnd() {
540
- if (this.thoughtSince === undefined) return;
541
- const seconds = Math.round((Date.now() - this.thoughtSince) / 1000);
542
- // How long it thought is not what the reader is here for, and a line of it
543
- // between every step broke every run of steps into singletons — which is
544
- // why nothing folded. The time is still on the status row while it runs.
545
- void seconds;
573
+ // The thought was the wait; once there is a reply it has nothing to add,
574
+ // so it comes off the screen rather than settling into the transcript.
575
+ if (this.thinkAt !== undefined) {
576
+ this.lines.splice(this.thinkAt, 1);
577
+ if (this.run && this.run.at > this.thinkAt) this.run.at--;
578
+ for (const run of this.segment?.values() ?? []) if (run.at > this.thinkAt) run.at--;
579
+ this.thinkAt = undefined;
580
+ this.render();
581
+ }
582
+ this.thought = '';
546
583
  this.thoughtSince = undefined;
547
584
  }
548
585
 
@@ -886,6 +923,7 @@ export class Screen {
886
923
  this.spinTimer = setInterval(() => {
887
924
  this.tick++;
888
925
  this.paintLiveRun();
926
+ this.paintLiveThought();
889
927
  this.paintStatus();
890
928
  }, FRAME_MS);
891
929
  this.spinTimer.unref?.();