ucode-agent 1.17.0 → 1.19.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.17.0",
3
+ "version": "1.19.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
@@ -458,13 +458,19 @@ function systemPrompt({ cwd, skills, mode, check, map, memory }) {
458
458
  'already contains it. Do not re-check work the checks have already reported on.',
459
459
  'Fast is not sloppy: it is the same work with the waiting taken out.',
460
460
  '',
461
- 'FIRST, EVERY TIME: one short line saying what you are about to do, then the tool',
462
- 'calls. Never open a turn with a tool call and no words. "Right, the HTML',
463
- 'structure first." / "Now the state and the render loop." / "That is the layout',
464
- 'done - onto the animations." / "Let me see what is there." One sentence, your own',
465
- 'voice, before the actions - not after them, not instead of them, not a',
466
- 'restatement of the request. The user watches this scroll past, and without those',
467
- 'lines it is a list of file operations they cannot read intent from.',
461
+ 'SPEAK AS "I", NEVER "WE". You are doing this, not a committee: "I will build',
462
+ 'Tide as a single HTML file", not "we have created the file".',
463
+ '',
464
+ 'Say three or four things over a whole build, not one per step:',
465
+ ' - Open with what you are going to make and how, in one line, before anything',
466
+ ' else: "I will build Tide as a single HTML file - markup, one stylesheet, one',
467
+ ' module - with the tasks kept in localStorage."',
468
+ ' - One line when you move between the big pieces of work: "The layout is done,',
469
+ ' now the animations."',
470
+ ' - One line at the end saying what it does and how to try it.',
471
+ 'That is all. A line before every tool call is not narration, it is noise: the',
472
+ 'steps already show on screen, and repeating them in words buries the few',
473
+ 'sentences worth reading.',
468
474
  '',
469
475
  'FIRST, EVERY TIME: write one short line saying what you are about to do, then',
470
476
  'make the tool calls. Never open a turn with a tool call and no words. Examples:',
@@ -931,6 +937,7 @@ export class Agent {
931
937
 
932
938
  this.busy = true;
933
939
  this.abort = new AbortController();
940
+ this.endedSilently = false;
934
941
 
935
942
  const turnStarted = Date.now();
936
943
  let finished = false;
@@ -956,6 +963,7 @@ export class Agent {
956
963
  this.stats.workMs += Date.now() - turnStarted;
957
964
  this.stats.turns++;
958
965
  if (!finished) this.closeInterrupted();
966
+ const ok = finished && !this.endedSilently;
959
967
  this.busy = false;
960
968
  this.abort = null;
961
969
  this.ui.stopSpinner();
@@ -966,7 +974,7 @@ export class Agent {
966
974
  if (finished) this.openWhenReady(turnStarted);
967
975
  // Last, so "Done" is the last thing that happens rather than the last
968
976
  // thing said before several more things happen.
969
- this.ui.turnEnd?.({ ok: finished });
977
+ this.ui.turnEnd?.({ ok });
970
978
  }
971
979
  }
972
980
 
@@ -1163,14 +1171,21 @@ export class Agent {
1163
1171
  this.push({
1164
1172
  role: 'user',
1165
1173
  content:
1166
- 'You finished without saying anything. In one or two sentences: what did ' +
1167
- 'you change, and does it work? No preamble, no repeating the diffs.',
1174
+ 'You stopped without saying anything. If the thing I asked for is not ' +
1175
+ 'built yet, carry on and build it. If it is, tell me in one or two ' +
1176
+ 'sentences what it does and how to try it. No preamble, no diffs.',
1168
1177
  });
1169
1178
  continue;
1170
1179
  }
1171
1180
 
1172
1181
  this.push({ role: 'assistant', content: reply.text });
1173
- if (!reply.text?.trim()) this.ui.note('the model ended the turn without a reply');
1182
+ if (!reply.text?.trim()) {
1183
+ // Silence after being asked to speak is not a finished turn. Saying
1184
+ // "Done" here is the worst thing available: the user believes it,
1185
+ // looks, and finds the thing they asked for was never built.
1186
+ this.ui.note('the model stopped without saying anything — the work may be unfinished');
1187
+ this.endedSilently = true;
1188
+ }
1174
1189
  return;
1175
1190
  }
1176
1191
 
@@ -639,7 +639,11 @@ export function describe(name, args = {}) {
639
639
  case 'deploy':
640
640
  return `Deploying ${clip(args.folder || '.', 30)} to Vercel`;
641
641
  case 'create_app':
642
- return `Creating ${clip(args.name || args.folder, 30)} from the Next.js starter`;
642
+ // Say which starter it actually is. Hardcoding one of them meant a plain
643
+ // HTML app announced itself as Next.js, which is a line that is simply
644
+ // untrue on screen while the opposite happens on disk.
645
+ return `Creating ${clip(args.name || args.folder, 30)} from the ` +
646
+ `${args.template === 'plain-html' ? 'HTML' : 'Next.js'} starter`;
643
647
  case 'look_at_app':
644
648
  return `Looking at ${clip(args.url, 40)} on a phone and a desktop`;
645
649
  case 'web_search':
@@ -192,9 +192,12 @@ export function fitActivity({ glyph, label = '', meta = [], hint = '', paint = d
192
192
  * theme; the rest dim, because it is a footnote to the answer above it rather
193
193
  * than something to read first.
194
194
  */
195
- export function doneLine(ms, steps) {
196
- const count = steps > 0 ? ` · ${steps} step${steps === 1 ? '' : 's'}` : '';
197
- return `${theme.ok('✓')} ${dim(`Done in ${formatDuration(ms)}${count}`)}`;
195
+ export function doneLine(ms, steps, { ok = true } = {}) {
196
+ // The step count is bookkeeping: it tells the reader nothing about whether
197
+ // the thing they asked for exists. /stats has it for anyone who wants it.
198
+ void steps;
199
+ if (!ok) return `${theme.warn('!')} ${dim(`Stopped after ${formatDuration(ms)} without finishing`)}`;
200
+ return `${theme.ok('✓')} ${dim(`Done in ${formatDuration(ms)}`)}`;
198
201
  }
199
202
 
200
203
  /** The step count, brighter for a moment right after it goes up. */
package/src/ui/plain.js CHANGED
@@ -262,7 +262,7 @@ export class Plain {
262
262
  turnEnd({ ok = true } = {}) {
263
263
  const t = this.turn;
264
264
  this.turn = null;
265
- if (t && ok && Date.now() - t.start >= 2000) this.write(` ${doneLine(Date.now() - t.start, t.steps)}`);
265
+ if (t && Date.now() - t.start >= 2000) this.write(` ${doneLine(Date.now() - t.start, t.steps, { ok })}`);
266
266
  }
267
267
 
268
268
  // -- input ---------------------------------------------------------------
package/src/ui/screen.js CHANGED
@@ -571,7 +571,9 @@ export class Screen {
571
571
  if (line.length < 12) return; // "Okay." tells nobody anything
572
572
 
573
573
  this.openedWith = line;
574
- this.push(narration(' ' + clip(line, Math.max(30, this.width() - 6))));
574
+ // Full strength: this is the model talking, and it is the thing on the page
575
+ // worth reading. The dimmed lines around it are the machinery.
576
+ this.push(' ' + chalk.white(clip(line, Math.max(30, this.width() - 6))));
575
577
  }
576
578
 
577
579
  thinkingEnd() {
@@ -910,7 +912,9 @@ export class Screen {
910
912
  const a = this.activity;
911
913
  this.activity = null;
912
914
  if (!this.status.busy) this.stopTimer();
913
- if (a && ok && Date.now() - a.start >= 2000) this.push(` ${doneLine(Date.now() - a.start, a.steps)}`);
915
+ // A turn that stopped without finishing says so. Dropping the line entirely
916
+ // left the transcript looking like the work was still going.
917
+ if (a && Date.now() - a.start >= 2000) this.push(` ${doneLine(Date.now() - a.start, a.steps, { ok })}`);
914
918
  this.paintStatus();
915
919
  }
916
920