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 +1 -1
- package/src/core/loop.js +7 -1
- package/src/tools/index.js +8 -2
- package/src/ui/screen.js +16 -16
- package/ucode.js +17 -4
package/package.json
CHANGED
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
|
-
|
|
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);
|
package/src/tools/index.js
CHANGED
|
@@ -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
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
358
|
+
paintRun() {
|
|
356
359
|
if (!this.run) return;
|
|
357
|
-
|
|
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);
|