ucode-agent 1.62.2 → 1.62.3

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/README.md CHANGED
@@ -27,7 +27,7 @@ corner:
27
27
  add a dark mode toggle that remembers the choice
28
28
 
29
29
 
30
- v1.62.2
30
+ v1.62.3
31
31
  ```
32
32
 
33
33
  A light crosses the wordmark once as it opens, and the three lines under the box
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ucode-agent",
3
- "version": "1.62.2",
3
+ "version": "1.62.3",
4
4
  "description": "ucode - a terminal coding agent that reads, edits and runs your code, on Google Gemini models.",
5
5
  "type": "module",
6
6
  "main": "ucode.js",
@@ -108,6 +108,18 @@ first time; then the server route with the real integration; then the core
108
108
  loop UI wired to it; then every state — empty, loading, success, error,
109
109
  invalid input; then polish: motion, responsive, copy, title and metadata.
110
110
 
111
+ ## 5a. It has to look finished
112
+
113
+ People judge the app in the first second it is on screen, before they click
114
+ anything. Before handing it over, check:
115
+
116
+ - A real title and one line saying what it is for — never "Welcome to…".
117
+ - The main action is visible without scrolling and is the most prominent button.
118
+ - Every button and input has hover and focus states; nothing is left browser-default.
119
+ - An empty list says what to do next ("No tasks yet — add one above"), never blank.
120
+ - It fits a 375px phone with no sideways scroll; tap targets are at least 44px.
121
+ - The number that matters (a score, a total, a timer) is big — several times body text.
122
+
111
123
  ## 5b. Tests, where there is a runner
112
124
 
113
125
  `next-shadcn` ships vitest and one passing test, so `npm test` works from
@@ -195,6 +195,18 @@ const res = await fetch('https://openrouter.ai/api/v1/chat/completions', {
195
195
 
196
196
  Use `batch_write` for the skeleton — one call, every file.
197
197
 
198
+ ## 5a. It has to look finished
199
+
200
+ People judge the app in the first second it is on screen, before they click
201
+ anything. Before handing it over, check:
202
+
203
+ - A real title and one line saying what it is for — never "Welcome to…".
204
+ - The main action is visible without scrolling and is the most prominent button.
205
+ - Every button and input has hover and focus states; nothing is left browser-default.
206
+ - An empty list says what to do next ("No tasks yet — add one above"), never blank.
207
+ - It fits a 375px phone with no sideways scroll; tap targets are at least 44px.
208
+ - The number that matters (a score, a total, a timer) is big — several times body text.
209
+
198
210
  ## 6. Prove it works
199
211
 
200
212
  - `npm run build` — it type-checks and lints; a build that fails is not done.
package/src/core/loop.js CHANGED
@@ -1246,7 +1246,8 @@ export class Agent {
1246
1246
  this.abort = new AbortController();
1247
1247
  this.endedSilently = false;
1248
1248
 
1249
- const turnStarted = Date.now();
1249
+ // Kept on the agent too: the hand-over says how long the build took.
1250
+ const turnStarted = (this.turnStarted = Date.now());
1250
1251
  let finished = false;
1251
1252
  this.ui.turnStart?.();
1252
1253
  try {
@@ -1592,8 +1593,7 @@ export class Agent {
1592
1593
  `${modelName()} produced invalid tool arguments ${argRetries} times running ` +
1593
1594
  'and could not correct itself.',
1594
1595
  fix:
1595
- 'Say what you want more concretely, or /model to another one — North Mini ' +
1596
- 'Code and Nemotron 3.5 Lightning are both steadier with tool arguments.',
1596
+ 'Say what you want more concretely, or /model to another one.',
1597
1597
  });
1598
1598
  }
1599
1599
  } else {
@@ -1750,13 +1750,29 @@ export class Agent {
1750
1750
  const server = serversReadySince(since).at(-1);
1751
1751
  if (!server || (this.opened ??= new Set()).has(server.url)) return;
1752
1752
  this.opened.add(server.url);
1753
+ if (this.openInBrowser(server.url)) this.ui.note(`Opened ${server.url} in your browser`);
1754
+ }
1755
+
1756
+ /**
1757
+ * Show a URL or a local page in the desktop browser. False when it did not.
1758
+ *
1759
+ * A file goes to explorer on Windows, not `cmd /c start`: the folder name
1760
+ * comes from the model, and an & in it would be a second command to cmd.
1761
+ */
1762
+ openInBrowser(target) {
1763
+ if (!this.full || process.env.UCODE_OPEN === '0') return false;
1764
+ const web = /^https?:\/\//.test(target);
1765
+ // Only pages inside the project: a UNC path would have explorer reach out to another machine.
1766
+ const rel = web ? '' : path.relative(this.cwd, target);
1767
+ if (!web && (!rel || rel.startsWith('..') || path.isAbsolute(rel))) return false;
1753
1768
  const [cmd, args] = process.platform === 'win32'
1754
- ? ['cmd', ['/c', 'start', '', server.url]]
1755
- : [process.platform === 'darwin' ? 'open' : 'xdg-open', [server.url]];
1769
+ ? (web ? ['cmd', ['/c', 'start', '', target]] : ['explorer.exe', [target]])
1770
+ : [process.platform === 'darwin' ? 'open' : 'xdg-open', [target]];
1756
1771
  try {
1757
- spawn(cmd, args, { stdio: 'ignore', detached: true, windowsHide: true }).unref();
1758
- this.ui.note(`Opened ${server.url} in your browser`);
1759
- } catch { /* no browser to open — the link is in the answer */ }
1772
+ // A missing opener (no xdg-open) fails later, as an event; unheard, it would crash ucode.
1773
+ spawn(cmd, args, { stdio: 'ignore', detached: true, windowsHide: true }).on('error', () => {}).unref();
1774
+ return true;
1775
+ } catch { return false; /* no browser to open — the link is in the answer */ }
1760
1776
  }
1761
1777
 
1762
1778
  cmdStats() {
@@ -2597,11 +2613,16 @@ export class Agent {
2597
2613
  const made = calls.find((c) => c.name === 'create_app' && c.args?.name)?.args.name ?? this.appName;
2598
2614
  const name = String(made ?? path.basename(app)).trim();
2599
2615
  this.appName = name;
2600
- const link = pathToFileURL(path.join(app, 'index.html')).href;
2601
- const text = `${name} is done. Open it here: ${link}\n\n(or open \`${show}/index.html\` in your browser)`;
2616
+ const page = path.join(app, 'index.html');
2617
+ const link = pathToFileURL(page).href;
2618
+ // "Quiz is done in 42s": how long the whole build took, from the request.
2619
+ const took = this.turnStarted ? ` in ${formatDuration(Date.now() - this.turnStarted)}` : '';
2620
+ const text = `${name} is done${took}. Open it here: ${link}\n\n(or open \`${show}/index.html\` in your browser)`;
2602
2621
  this.push({ role: 'assistant', content: text });
2603
2622
  this.ui.assistant(text, { closing: true });
2604
2623
  await this.persist();
2624
+ // The app pops up on its own; UCODE_OPEN=0 keeps it to the link.
2625
+ this.openInBrowser(page);
2605
2626
  return { done: true };
2606
2627
  }
2607
2628
 
@@ -500,9 +500,8 @@ export function explain(err, id) {
500
500
  : `Too many requests for ${modelName(id)} just now${wait ? ` — clear in ${wait}` : ''}.`,
501
501
  fix: daily
502
502
  ? `It resets ${resetAt ? `at ${resetAt.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}` : 'once a day'}. ` +
503
- 'Adding credit to your account raises the limit.'
504
- : 'ucode waits these out on its own. Free endpoints are shared, so it usually ' +
505
- 'clears in seconds; /model moves to a quieter one.',
503
+ 'Nothing to do until then — the key is fine.'
504
+ : 'ucode waits these out on its own, usually for a few seconds. /model moves to another one.',
506
505
  detail: { retryAfter, daily, resetAt: resetAt?.getTime() ?? null },
507
506
  cause: err,
508
507
  });
@@ -596,10 +595,7 @@ export function explain(err, id) {
596
595
  kind: 'timeout',
597
596
  attempted,
598
597
  failed: `${modelName(id)} sent nothing back in time — the provider dropped the request.`,
599
- fix:
600
- 'Free endpoints stall under load, and the largest reasoning models are the ' +
601
- 'first to. ucode already retried. If it keeps happening, /model to Nemotron ' +
602
- '3.5 Lightning or North Mini Code, which answer sooner.',
598
+ fix: 'ucode already retried. Google is busy right now — send again, or /model to another one.',
603
599
  detail: { status },
604
600
  cause: err,
605
601
  });
@@ -609,7 +605,7 @@ export function explain(err, id) {
609
605
  return new Failure({
610
606
  kind: 'server',
611
607
  attempted,
612
- failed: `The provider returned HTTP ${status}. That is their side, not yours.`,
608
+ failed: `Google's servers are busy right now (HTTP ${status}). That is their side, not yours.`,
613
609
  fix: 'Wait a few seconds and send again. If it persists, /model to another one.',
614
610
  cause: err,
615
611
  });
@@ -627,11 +623,14 @@ export function explain(err, id) {
627
623
  return new Failure({
628
624
  kind: 'network',
629
625
  attempted,
630
- failed: `The connection to the model dropped: ${raw}`,
626
+ // No DNS answer: the machine is offline, and saying so
627
+ // beats a raw "getaddrinfo ENOTFOUND" on a demo screen.
628
+ failed: /ENOTFOUND|EAI_AGAIN|getaddrinfo/i.test(raw)
629
+ ? 'ucode could not reach Google — this computer looks offline.'
630
+ : `The connection to Google dropped: ${raw}`,
631
631
  fix:
632
- 'ucode retries this by itself. If it keeps happening, check your connection, ' +
633
- 'VPN and any corporate proxy (HTTPS_PROXY) — or /model to a lighter one, since ' +
634
- 'a long think on a busy free endpoint is the usual cause.',
632
+ 'ucode retries this by itself. If it keeps happening, check the Wi-Fi, ' +
633
+ 'VPN and any proxy (HTTPS_PROXY), then send again.',
635
634
  cause: err,
636
635
  });
637
636
  }
@@ -785,7 +784,7 @@ async function streamed(request, opts, id) {
785
784
  kind: 'timeout',
786
785
  attempted: `asking ${modelName(id)} for a reply`,
787
786
  failed: `${modelName(id)} went silent for ${Math.round(limit() / 1000)}s, so ucode stopped waiting.`,
788
- fix: 'ucode asks again by itself. If it keeps freezing, /model to North Mini Code.',
787
+ fix: 'ucode asks again by itself. If it keeps freezing, /model to another one.',
789
788
  detail: { stalled: true, handed: handed.size },
790
789
  cause,
791
790
  });