ucode-agent 1.13.0 → 1.14.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.13.0",
3
+ "version": "1.14.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,12 +434,24 @@ function systemPrompt({ cwd, skills, mode, check, map, memory }) {
434
434
  '',
435
435
  '## How to work',
436
436
  '',
437
- '- Say what you are about to do before you do it, in one short line, every time you',
438
- ' pick up a new piece of work: "Right, the HTML structure first.", "Now the state',
439
- ' and the render loop.", "That is the layout done - onto the animations." Without',
440
- ' it the screen is a list of file operations and the user cannot tell what you are',
441
- ' building or why. One sentence, in your own voice, then the actions. Not three',
442
- ' sentences, and not a restatement of the request.',
437
+ 'FIRST, EVERY TIME: one short line saying what you are about to do, then the tool',
438
+ 'calls. Never open a turn with a tool call and no words. "Right, the HTML',
439
+ 'structure first." / "Now the state and the render loop." / "That is the layout',
440
+ 'done - onto the animations." / "Let me see what is there." One sentence, your own',
441
+ 'voice, before the actions - not after them, not instead of them, not a',
442
+ 'restatement of the request. The user watches this scroll past, and without those',
443
+ 'lines it is a list of file operations they cannot read intent from.',
444
+ '',
445
+ 'FIRST, EVERY TIME: write one short line saying what you are about to do, then',
446
+ 'make the tool calls. Never open a turn with a tool call and no words. Examples:',
447
+ '"Right, the HTML structure first." / "Now the state and the render loop." /',
448
+ '"That is the layout done - onto the animations." / "Let me see what is there."',
449
+ 'One sentence, your own voice, before the actions - not after them, not instead',
450
+ 'of them, and not a restatement of what was asked. The user is watching this',
451
+ 'scroll past; without those lines it is a list of file operations and they cannot',
452
+ 'tell what you are building. This matters as much as the code.',
453
+ '',
454
+
443
455
  '',
444
456
  'Before you guess at an API, ask: type_of gives the exact signature from the',
445
457
  'TypeScript this project has installed, and find_symbol says where something is declared without',
@@ -1370,9 +1382,13 @@ export class Agent {
1370
1382
  reportFailure(call, err) {
1371
1383
  if (!(err instanceof ToolFailure)) throw err;
1372
1384
 
1373
- this.ui.toolFailed(
1374
- err instanceof Declined ? 'declined' : `${err.kind}: ${err.failed}`
1375
- );
1385
+ // Bad arguments are the model talking to itself. "old_string and new_string
1386
+ // are identical" is a correction it will make on the next step, and it means
1387
+ // nothing to whoever is watching except that something went wrong. It goes
1388
+ // to the model, which can act on it, and not to the screen. A refusal the
1389
+ // user made, and anything that actually failed, still shows.
1390
+ if (err instanceof Declined) this.ui.toolFailed('declined');
1391
+ else if (err.kind !== 'bad_args') this.ui.toolFailed(`${err.kind}: ${err.failed}`);
1376
1392
  this.push({
1377
1393
  role: 'tool',
1378
1394
  toolCallId: call.id,
package/src/ui/screen.js CHANGED
@@ -297,40 +297,66 @@ export class Screen {
297
297
  // U+25CF, not U+23FA: the latter carries emoji presentation, which Windows
298
298
  // Terminal draws as a white circle on a blue tile.
299
299
  const kind = groupKind(label);
300
- const run = this.run;
301
- // The run's own line is either the last one, or the last but one with its
302
- // result underneath. Anything further down means something else was said
303
- // in between, and the run is over.
304
- const gap = run ? this.lines.length - 1 - run.at : Infinity;
305
-
306
- // A second step of the same kind rewrites the line the first one wrote,
307
- // rather than adding another almost-identical one beneath it.
308
- if (run && run.kind === kind && gap <= 1) {
309
- if (gap === 1) this.lines.pop(); // its single result line, now counted
300
+ // One line per kind of work for as long as the model is working on one
301
+ // thing. Reading, writing and reading again used to draw six lines that
302
+ // said three things; now the "Reading files" line it already has is the
303
+ // one that counts up, wherever it sits.
304
+ const run = (this.segment ??= new Map()).get(kind);
305
+
306
+ if (run && this.lines[run.at] !== undefined) {
310
307
  run.count++;
311
308
  run.label = label;
312
309
  run.targets.push(groupTarget(label));
313
- this.paintRun();
310
+ this.run = run;
311
+ this.paintRun({ live: true });
314
312
  } else {
315
313
  this.push(`${narrationMark()} ${narration(asLabel(label))}`);
316
314
  this.run = {
317
315
  kind, count: 1, at: this.lines.length - 1, label,
318
316
  targets: [groupTarget(label)], added: 0, removed: 0,
319
317
  };
318
+ this.segment.set(kind, this.run);
319
+ this.paintRun({ live: true });
320
320
  }
321
321
  this.updateSpinner(label);
322
322
  }
323
323
 
324
- /** Anything that is not another step of the same kind ends the run. */
325
- endRun() { this.run = null; }
324
+ /**
325
+ * The model speaking or a plan, or a failure — ends the segment.
326
+ *
327
+ * Up to that point a kind of work keeps one line and counts up on it. After
328
+ * it, the next read is a new piece of work and deserves its own line, which
329
+ * is what makes the transcript read as a sequence of things done rather
330
+ * than a set of running totals.
331
+ */
332
+ endRun() {
333
+ if (this.run) this.paintRun({ live: false });
334
+ this.run = null;
335
+ this.segment = new Map();
336
+ }
326
337
 
327
338
  /** Redraw the run's single line from what it has accumulated. */
328
- paintRun() {
339
+ /**
340
+ * Redraw the run's single line from what it has accumulated.
341
+ *
342
+ * While its step is still running the text shimmers, which is the only
343
+ * thing on screen saying "this is happening now" once the per-step result
344
+ * lines are gone. It settles to plain dim the moment the step finishes, so
345
+ * the finished ones above stay quiet.
346
+ */
347
+ paintRun({ live = this.run?.live } = {}) {
329
348
  if (!this.run) return;
330
- this.lines[this.run.at] = `${narrationMark()} ${narration(asLabel(runLine(this.run)))}`;
349
+ const text = asLabel(runLine(this.run));
350
+ this.run.live = live;
351
+ this.lines[this.run.at] = `${narrationMark()} ${live ? shimmer(text, this.tick * FRAME_MS) : narration(text)}`;
331
352
  this.render();
332
353
  }
333
354
 
355
+ /** Let the line in flight animate, one frame per tick. */
356
+ paintLiveRun() {
357
+ if (this.run?.live && this.lines[this.run.at] !== undefined) this.paintRun({ live: true });
358
+ }
359
+
334
360
  /**
335
361
  * A change, as its two numbers.
336
362
  *
@@ -361,7 +387,10 @@ export class Screen {
361
387
  * already names the step, and a change adds its numbers to that same line.
362
388
  * Only a failure earns a line of its own.
363
389
  */
364
- toolResult() {}
390
+ toolResult() {
391
+ // The step is over: the line stops moving and joins the quiet ones above.
392
+ if (this.run) this.paintRun({ live: false });
393
+ }
365
394
 
366
395
  toolFailed(summary) {
367
396
  // A failure is never folded away.
@@ -856,6 +885,7 @@ export class Screen {
856
885
  if (this.spinTimer) return;
857
886
  this.spinTimer = setInterval(() => {
858
887
  this.tick++;
888
+ this.paintLiveRun();
859
889
  this.paintStatus();
860
890
  }, FRAME_MS);
861
891
  this.spinTimer.unref?.();