ucode-agent 1.18.0 → 1.19.1

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.18.0",
3
+ "version": "1.19.1",
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
@@ -937,6 +937,7 @@ export class Agent {
937
937
 
938
938
  this.busy = true;
939
939
  this.abort = new AbortController();
940
+ this.endedSilently = false;
940
941
 
941
942
  const turnStarted = Date.now();
942
943
  let finished = false;
@@ -962,6 +963,7 @@ export class Agent {
962
963
  this.stats.workMs += Date.now() - turnStarted;
963
964
  this.stats.turns++;
964
965
  if (!finished) this.closeInterrupted();
966
+ const ok = finished && !this.endedSilently;
965
967
  this.busy = false;
966
968
  this.abort = null;
967
969
  this.ui.stopSpinner();
@@ -972,7 +974,7 @@ export class Agent {
972
974
  if (finished) this.openWhenReady(turnStarted);
973
975
  // Last, so "Done" is the last thing that happens rather than the last
974
976
  // thing said before several more things happen.
975
- this.ui.turnEnd?.({ ok: finished });
977
+ this.ui.turnEnd?.({ ok });
976
978
  }
977
979
  }
978
980
 
@@ -1169,14 +1171,21 @@ export class Agent {
1169
1171
  this.push({
1170
1172
  role: 'user',
1171
1173
  content:
1172
- 'You finished without saying anything. In one or two sentences: what did ' +
1173
- '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.',
1174
1177
  });
1175
1178
  continue;
1176
1179
  }
1177
1180
 
1178
1181
  this.push({ role: 'assistant', content: reply.text });
1179
- 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
+ }
1180
1189
  return;
1181
1190
  }
1182
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
@@ -912,7 +912,9 @@ export class Screen {
912
912
  const a = this.activity;
913
913
  this.activity = null;
914
914
  if (!this.status.busy) this.stopTimer();
915
- 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 })}`);
916
918
  this.paintStatus();
917
919
  }
918
920
 
package/ucode.js CHANGED
@@ -88,7 +88,13 @@ async function main() {
88
88
  // have no way to know. So if there is something newer, it goes on now and
89
89
  // this process hands over to it. The wait is a few seconds, once per
90
90
  // release; every other launch pays one quick question to the registry.
91
- if (!args.version && process.argv[2] !== 'login' && process.argv[2] !== 'doctor') {
91
+ //
92
+ // Only when a person is sitting there. Handing over means re-running this
93
+ // process, and a run whose input is a pipe has that input consumed by the
94
+ // handover — `cat prompt.txt | ucode` printed "updating…" and then did
95
+ // nothing at all. Piped runs take the background update and carry on.
96
+ if (!args.version && process.stdin.isTTY &&
97
+ process.argv[2] !== 'login' && process.argv[2] !== 'doctor') {
92
98
  const { pendingUpdate, installNow } = await import('./src/core/updater.js');
93
99
  const waiting = await pendingUpdate();
94
100
  if (waiting) {