ucode-agent 1.20.1 → 1.22.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.20.1",
3
+ "version": "1.22.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
@@ -459,6 +459,12 @@ function systemPrompt({ cwd, skills, mode, check, map, memory }) {
459
459
  'already contains it. Do not re-check work the checks have already reported on.',
460
460
  'Fast is not sloppy: it is the same work with the waiting taken out.',
461
461
  '',
462
+ 'Never repeat the request back. Not as a summary, not as a restatement, not as',
463
+ 'a list of what was asked for. They wrote it and it is on the screen above you.',
464
+ 'Do not narrate your planning either - which files you will make, what order you',
465
+ 'will do them in, what you are about to consider. Say what you are building and',
466
+ 'then build it.',
467
+ '',
462
468
  'SPEAK AS "I", NEVER "WE". You are doing this, not a committee: "I will build',
463
469
  'Tide as a single HTML file", not "we have created the file".',
464
470
  '',
@@ -1411,7 +1417,7 @@ export class Agent {
1411
1417
  // A build or type check that just passed already verified everything
1412
1418
  // changed so far; the automatic check at the end would only repeat it.
1413
1419
  if (call.name === 'run_command' && out.exitCode === 0 &&
1414
- /(?:next build|npm run build|pnpm (?:run )?build|tsc)/.test(call.args?.command ?? '')) {
1420
+ /(?:next build|npm run build|pnpm (?:run )?build|tsc)/.test(call.args?.command ?? '')) {
1415
1421
  this.sinceCheck?.clear();
1416
1422
  }
1417
1423
  if (!QUIET.has(call.name)) this.ui.toolResult(out.summary);
@@ -12,7 +12,6 @@ import { addBlock, BLOCK_NAMES } from './blocks.js';
12
12
  import { typeOf } from './types.js';
13
13
  import { runCommand, runCommands } from './shell.js';
14
14
  import { webSearch } from './web.js';
15
- import { lookAtApp } from './browser.js';
16
15
  import { createApp, TEMPLATE_NAMES, TEMPLATE_NOTES } from './scaffold.js';
17
16
  import { deploy } from './deploy.js';
18
17
  import { clip, READ_LINES } from './shared.js';
@@ -492,7 +491,14 @@ const run = {
492
491
  run_command: runCommand,
493
492
  run_commands: runCommands,
494
493
  web_search: webSearch,
495
- look_at_app: lookAtApp,
494
+ /**
495
+ * Loaded when it is used, not when ucode starts.
496
+ *
497
+ * browser.js pulls in playwright-core, which costs a second of start-up on
498
+ * its own. Nothing reaches for the browser until someone types /look, so
499
+ * nobody should wait for it before the first prompt.
500
+ */
501
+ look_at_app: async (args, opts) => (await import('./browser.js')).lookAtApp(args, opts),
496
502
  create_app: createApp,
497
503
  deploy,
498
504
  };
package/src/ui/screen.js CHANGED
@@ -106,6 +106,9 @@ const CHROME_BELOW = 6;
106
106
  /** How long one sentence of reasoning holds the line before the next takes it. */
107
107
  const THOUGHT_HOLD_MS = 1100;
108
108
 
109
+ /** Reasoning that is about the request rather than about the work. */
110
+ const RESTATEMENT = /^(?:the user|they|so the user|user)|^(?:i (?:need|should|will need) to (?:understand|figure|work out|check what))|^(?:let me (?:understand|re-?read|look at the (?:request|prompt)))|^(?:the (?:request|prompt|task) (?:is|asks|says))/i;
111
+
109
112
  /** The wordmark only earns its place with room for the facts column beside it. */
110
113
  const WORDMARK_NEEDS = BANNER_WIDTH + 30;
111
114
 
@@ -316,7 +319,7 @@ export class Screen {
316
319
  run.label = label;
317
320
  run.targets.push(groupTarget(label));
318
321
  this.run = run;
319
- this.paintRun({ live: true });
322
+ this.paintRun();
320
323
  } else {
321
324
  this.push(`${narrationMark()} ${narration(asLabel(label))}`);
322
325
  this.run = {
@@ -324,7 +327,7 @@ export class Screen {
324
327
  targets: [groupTarget(label)], added: 0, removed: 0,
325
328
  };
326
329
  this.segment.set(kind, this.run);
327
- this.paintRun({ live: true });
330
+ this.paintRun();
328
331
  }
329
332
  this.updateSpinner(label);
330
333
  }
@@ -352,19 +355,12 @@ export class Screen {
352
355
  * lines are gone. It settles to plain dim the moment the step finishes, so
353
356
  * the finished ones above stay quiet.
354
357
  */
355
- paintRun({ live = this.run?.live } = {}) {
358
+ paintRun() {
356
359
  if (!this.run) return;
357
- const text = asLabel(runLine(this.run));
358
- this.run.live = live;
359
- this.lines[this.run.at] = `${narrationMark()} ${live ? shimmer(text, this.tick * FRAME_MS) : narration(text)}`;
360
+ this.lines[this.run.at] = `${narrationMark()} ${narration(asLabel(runLine(this.run)))}`;
360
361
  this.render();
361
362
  }
362
363
 
363
- /** Let the line in flight animate, one frame per tick. */
364
- paintLiveRun() {
365
- if (this.run?.live && this.lines[this.run.at] !== undefined) this.paintRun({ live: true });
366
- }
367
-
368
364
  /**
369
365
  * A change, as its two numbers.
370
366
  *
@@ -395,10 +391,7 @@ export class Screen {
395
391
  * already names the step, and a change adds its numbers to that same line.
396
392
  * Only a failure earns a line of its own.
397
393
  */
398
- toolResult() {
399
- // The step is over: the line stops moving and joins the quiet ones above.
400
- if (this.run) this.paintRun({ live: false });
401
- }
394
+ toolResult() {}
402
395
 
403
396
  /**
404
397
  * Something went wrong, and the model is the one who can do anything about it.
@@ -569,6 +562,11 @@ export class Screen {
569
562
 
570
563
  const line = finished[1].trim();
571
564
  if (line.length < 12) return; // "Okay." tells nobody anything
565
+ // Reasoning models open by restating the request to themselves — "The user
566
+ // wants a tasks app called Tide" — which the user wrote, is looking at, and
567
+ // does not need read back. Only a sentence about what is being done is
568
+ // worth the line.
569
+ if (RESTATEMENT.test(line)) { this.thought = ''; return; }
572
570
 
573
571
  this.openedWith = line;
574
572
  // Full strength: this is the model talking, and it is the thing on the page
@@ -922,8 +920,10 @@ export class Screen {
922
920
  startTimer() {
923
921
  if (this.spinTimer) return;
924
922
  this.spinTimer = setInterval(() => {
923
+ // Only the status row repaints on a tick. Animating a transcript line
924
+ // meant redrawing the whole frame twelve times a second, and the input
925
+ // box was being rebuilt under the user's cursor as they typed.
925
926
  this.tick++;
926
- this.paintLiveRun();
927
927
  this.paintStatus();
928
928
  }, FRAME_MS);
929
929
  this.spinTimer.unref?.();
package/ucode.js CHANGED
@@ -12,12 +12,22 @@ import { readFile } from 'node:fs/promises';
12
12
  import { realpathSync } from 'node:fs';
13
13
  import { pathToFileURL, fileURLToPath } from 'node:url';
14
14
 
15
- import { Agent } from './src/core/loop.js';
16
- import { setModel, modelName, MODELS, DEFAULT_MODEL, ENV_FILE } from './src/core/provider.js';
17
15
  import { VERSION } from './src/core/version.js';
18
- import { Plain } from './src/ui/plain.js';
19
16
  import { blue, dim, sky } from './src/ui/theme.js';
20
17
 
18
+ /**
19
+ * The agent and the provider are loaded when a session actually starts.
20
+ *
21
+ * Between them they pull in the OpenAI SDK and every tool, which is most of
22
+ * the two and a half seconds ucode used to take before printing anything at
23
+ * all — including for `--version`, which needs none of it.
24
+ */
25
+ const heavy = () => Promise.all([
26
+ import('./src/core/loop.js'),
27
+ import('./src/core/provider.js'),
28
+ import('./src/ui/plain.js'),
29
+ ]);
30
+
21
31
  function parseArgs(argv) {
22
32
  const args = { debug: false, model: null, cwd: process.cwd(), help: false, plan: false, version: false };
23
33
 
@@ -34,7 +44,8 @@ function parseArgs(argv) {
34
44
  return args;
35
45
  }
36
46
 
37
- function usage() {
47
+ async function usage() {
48
+ const [, { MODELS, modelName, DEFAULT_MODEL, ENV_FILE }] = await heavy();
38
49
  const entries = Object.entries(MODELS);
39
50
  const width = Math.max(...entries.map(([, m]) => m.name.length));
40
51
  const models = entries
@@ -112,6 +123,8 @@ async function main() {
112
123
  }
113
124
  }
114
125
 
126
+ const [{ Agent }, { setModel }, { Plain }] = await heavy();
127
+
115
128
  if (args.model) {
116
129
  try {
117
130
  setModel(args.model);