ucode-agent 1.48.0 → 1.50.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/README.md +5 -3
- package/package.json +5 -3
- package/src/core/loop.js +3022 -3014
- package/src/core/provider.js +18 -6
- package/src/tools/browser.js +13 -4
- package/src/tools/index.js +5 -3
- package/src/tools/scaffold.js +1 -1
package/src/core/provider.js
CHANGED
|
@@ -46,11 +46,11 @@ dotenv.config({ path: join(PACKAGE_ROOT, '.env'), quiet: true });
|
|
|
46
46
|
/**
|
|
47
47
|
* The whole model list. Not a starting point — the list.
|
|
48
48
|
*
|
|
49
|
-
* ucode runs on NVIDIA, Cohere
|
|
50
|
-
* capable models free through OpenRouter, all handle tool
|
|
51
|
-
* and keeping the set to
|
|
52
|
-
* rather than listed on the strength of a benchmark. A
|
|
53
|
-
* models is a picker nobody reads.
|
|
49
|
+
* ucode runs on NVIDIA, Cohere, Nex AGI, DeepSeek and Qwen only. All of them
|
|
50
|
+
* serve genuinely capable models free through OpenRouter, all handle tool
|
|
51
|
+
* calling properly, and keeping the set to eight means every one of them has
|
|
52
|
+
* been used in anger rather than listed on the strength of a benchmark. A
|
|
53
|
+
* picker offering sixty models is a picker nobody reads.
|
|
54
54
|
*
|
|
55
55
|
* `name` is what the status bar shows. `note` is what the picker shows.
|
|
56
56
|
*/
|
|
@@ -89,6 +89,18 @@ export const MODELS = {
|
|
|
89
89
|
context: 262_144,
|
|
90
90
|
note: 'new agentic coder, on trial — can stall on big builds',
|
|
91
91
|
},
|
|
92
|
+
// Both free on OpenRouter with tool calling, added untested: on trial, not
|
|
93
|
+
// the default and not in FALLBACKS until a real ucode build has run on them.
|
|
94
|
+
'deepseek/deepseek-v4-flash-0731:free': {
|
|
95
|
+
name: 'DeepSeek V4 Flash',
|
|
96
|
+
context: 1_048_576,
|
|
97
|
+
note: 'fast DeepSeek coder, 1M context — on trial',
|
|
98
|
+
},
|
|
99
|
+
'qwen/qwen3.8-27b:free': {
|
|
100
|
+
name: 'Qwen 3.8',
|
|
101
|
+
context: 262_144,
|
|
102
|
+
note: 'compact Qwen coder, 262k context — on trial',
|
|
103
|
+
},
|
|
92
104
|
};
|
|
93
105
|
|
|
94
106
|
/**
|
|
@@ -173,7 +185,7 @@ export function setModel(id) {
|
|
|
173
185
|
throw new Failure({
|
|
174
186
|
kind: 'bad_model',
|
|
175
187
|
attempted: `switching to "${wanted}"`,
|
|
176
|
-
failed: 'ucode only runs NVIDIA, Cohere
|
|
188
|
+
failed: 'ucode only runs NVIDIA, Cohere, Nex AGI, DeepSeek and Qwen models, and that is not one of them.',
|
|
177
189
|
fix: `Run /model to choose from: ${Object.keys(MODELS).join(', ')}`,
|
|
178
190
|
});
|
|
179
191
|
}
|
package/src/tools/browser.js
CHANGED
|
@@ -188,7 +188,11 @@ export function forgetReviews() {
|
|
|
188
188
|
const PROBE_TEXT = 'ucode check';
|
|
189
189
|
|
|
190
190
|
/** Did anything at all happen on the page? */
|
|
191
|
-
|
|
191
|
+
// The markup itself, not its length: a timer going 25:00 -> 24:59, a counter
|
|
192
|
+
// going 0 -> 1 or a task ticked off by a class change keeps every length the
|
|
193
|
+
// same, and was reported as "NOTHING HAPPENS" — a false accusation the model
|
|
194
|
+
// then "fixed" by rewriting script that worked.
|
|
195
|
+
const moved = (a, b) => a.nodes !== b.nodes || a.html !== b.html || a.stored !== b.stored;
|
|
192
196
|
|
|
193
197
|
/**
|
|
194
198
|
* Use the app, rather than only looking at it.
|
|
@@ -218,6 +222,7 @@ async function useTheApp(page) {
|
|
|
218
222
|
const snapshot = () => page.evaluate(() => ({
|
|
219
223
|
nodes: document.body.querySelectorAll('*').length,
|
|
220
224
|
text: document.body.innerText.replace(/\s+/g, ' ').trim().length,
|
|
225
|
+
html: document.body.innerHTML,
|
|
221
226
|
stored: (() => { try { return JSON.stringify(localStorage).length; } catch { return 0; } })(),
|
|
222
227
|
}));
|
|
223
228
|
|
|
@@ -326,7 +331,7 @@ export async function withStaticServer(dir, fn) {
|
|
|
326
331
|
}
|
|
327
332
|
}
|
|
328
333
|
|
|
329
|
-
export async function lookAtApp({ url, paths = ['/'] }) {
|
|
334
|
+
export async function lookAtApp({ url, paths = ['/'], review: withReview = true }) {
|
|
330
335
|
const base = String(url ?? '').trim().replace(/\/+$/, '');
|
|
331
336
|
if (!LOCAL.test(`${base}/`)) {
|
|
332
337
|
throw new ToolFailure({
|
|
@@ -355,6 +360,10 @@ export async function lookAtApp({ url, paths = ['/'] }) {
|
|
|
355
360
|
const errors = [];
|
|
356
361
|
const failed = [];
|
|
357
362
|
page.on('console', (m) => {
|
|
363
|
+
// The browser asks for /favicon.ico on its own. A page without one is
|
|
364
|
+
// not broken, and counting that 404 sent every plain-html build round
|
|
365
|
+
// a fix loop it did not need.
|
|
366
|
+
if (/\/favicon\.ico$/i.test(m.location()?.url ?? '')) return;
|
|
358
367
|
if (m.type() === 'error' && !/devtools|download the react/i.test(m.text())) errors.push(m.text().slice(0, 200));
|
|
359
368
|
});
|
|
360
369
|
page.on('pageerror', (e) => errors.push(`uncaught: ${String(e.message).slice(0, 200)}`));
|
|
@@ -513,7 +522,7 @@ export async function lookAtApp({ url, paths = ['/'] }) {
|
|
|
513
522
|
// error overlay is a minute spent on nothing.
|
|
514
523
|
let critique = '';
|
|
515
524
|
const state = reviews.get(base) ?? { done: false, tries: 0 };
|
|
516
|
-
if (!broken && !state.done && state.tries < 2 && toReview.length) {
|
|
525
|
+
if (withReview && !broken && !state.done && state.tries < 2 && toReview.length) {
|
|
517
526
|
state.tries++;
|
|
518
527
|
reviews.set(base, state);
|
|
519
528
|
try {
|
|
@@ -530,7 +539,7 @@ export async function lookAtApp({ url, paths = ['/'] }) {
|
|
|
530
539
|
critique ? `## Visual review\n${critique}` : '',
|
|
531
540
|
'',
|
|
532
541
|
problems
|
|
533
|
-
? 'Fix the problems above
|
|
542
|
+
? 'Fix the problems above - only those; leave everything that works as it is.'
|
|
534
543
|
: state.done && critique
|
|
535
544
|
? 'The automatic checks found nothing. Weigh the visual review, fix what is worth fixing - ' +
|
|
536
545
|
'the next look re-runs only the fast checks.'
|
package/src/tools/index.js
CHANGED
|
@@ -12,7 +12,7 @@ import { addBlock, BLOCK_NAMES, PLAIN_BLOCK_NAMES, ALL_BLOCK_NAMES } from './blo
|
|
|
12
12
|
import { typeOf } from './types.js';
|
|
13
13
|
import { runCommand, runCommands } from './shell.js';
|
|
14
14
|
import { webSearch } from './web.js';
|
|
15
|
-
import { createApp, TEMPLATE_NAMES, TEMPLATE_NOTES } from './scaffold.js';
|
|
15
|
+
import { createApp, normaliseFiles, TEMPLATE_NAMES, TEMPLATE_NOTES } from './scaffold.js';
|
|
16
16
|
import { deploy } from './deploy.js';
|
|
17
17
|
import { clip, READ_LINES } from './shared.js';
|
|
18
18
|
|
|
@@ -702,13 +702,15 @@ export function describe(name, args = {}) {
|
|
|
702
702
|
return `Running ${args.commands?.length ?? 0} commands together`;
|
|
703
703
|
case 'deploy':
|
|
704
704
|
return `Deploying ${clip(args.folder || '.', 30)} to Vercel`;
|
|
705
|
-
case 'create_app':
|
|
705
|
+
case 'create_app': {
|
|
706
|
+
const files = normaliseFiles(args.files).length;
|
|
706
707
|
// Say which starter it actually is. Hardcoding one of them meant a plain
|
|
707
708
|
// HTML app announced itself as Next.js, which is a line that is simply
|
|
708
709
|
// untrue on screen while the opposite happens on disk.
|
|
709
710
|
return `Creating ${clip(args.name || args.folder, 30)} from the ` +
|
|
710
711
|
`${args.template === 'next-shadcn' ? 'Next.js' : 'HTML'} starter` +
|
|
711
|
-
`${
|
|
712
|
+
`${files ? ` with ${files} file${files === 1 ? '' : 's'}` : ''}`;
|
|
713
|
+
}
|
|
712
714
|
case 'look_at_app':
|
|
713
715
|
return `Looking at ${clip(args.url, 40)} on a phone and a desktop`;
|
|
714
716
|
case 'web_search':
|
package/src/tools/scaffold.js
CHANGED
|
@@ -48,7 +48,7 @@ const SHOW_BACK = { 'plain-html': ['index.html', 'styles.css', 'app.js'] };
|
|
|
48
48
|
* something adjacent. Each of those, refused, is a round trip spent being
|
|
49
49
|
* told what could have been read — so they are all read.
|
|
50
50
|
*/
|
|
51
|
-
function normaliseFiles(files) {
|
|
51
|
+
export function normaliseFiles(files) {
|
|
52
52
|
let value = files;
|
|
53
53
|
if (typeof value === 'string') {
|
|
54
54
|
try { value = JSON.parse(value); } catch { return []; }
|