ucode-agent 1.13.0 → 1.14.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 +25 -9
- package/src/ui/screen.js +46 -16
package/package.json
CHANGED
package/src/core/loop.js
CHANGED
|
@@ -434,12 +434,24 @@ function systemPrompt({ cwd, skills, mode, check, map, memory }) {
|
|
|
434
434
|
'',
|
|
435
435
|
'## How to work',
|
|
436
436
|
'',
|
|
437
|
-
'
|
|
438
|
-
'
|
|
439
|
-
'
|
|
440
|
-
'
|
|
441
|
-
'
|
|
442
|
-
'
|
|
437
|
+
'FIRST, EVERY TIME: one short line saying what you are about to do, then the tool',
|
|
438
|
+
'calls. Never open a turn with a tool call and no words. "Right, the HTML',
|
|
439
|
+
'structure first." / "Now the state and the render loop." / "That is the layout',
|
|
440
|
+
'done - onto the animations." / "Let me see what is there." One sentence, your own',
|
|
441
|
+
'voice, before the actions - not after them, not instead of them, not a',
|
|
442
|
+
'restatement of the request. The user watches this scroll past, and without those',
|
|
443
|
+
'lines it is a list of file operations they cannot read intent from.',
|
|
444
|
+
'',
|
|
445
|
+
'FIRST, EVERY TIME: write one short line saying what you are about to do, then',
|
|
446
|
+
'make the tool calls. Never open a turn with a tool call and no words. Examples:',
|
|
447
|
+
'"Right, the HTML structure first." / "Now the state and the render loop." /',
|
|
448
|
+
'"That is the layout done - onto the animations." / "Let me see what is there."',
|
|
449
|
+
'One sentence, your own voice, before the actions - not after them, not instead',
|
|
450
|
+
'of them, and not a restatement of what was asked. The user is watching this',
|
|
451
|
+
'scroll past; without those lines it is a list of file operations and they cannot',
|
|
452
|
+
'tell what you are building. This matters as much as the code.',
|
|
453
|
+
'',
|
|
454
|
+
|
|
443
455
|
'',
|
|
444
456
|
'Before you guess at an API, ask: type_of gives the exact signature from the',
|
|
445
457
|
'TypeScript this project has installed, and find_symbol says where something is declared without',
|
|
@@ -1370,9 +1382,13 @@ export class Agent {
|
|
|
1370
1382
|
reportFailure(call, err) {
|
|
1371
1383
|
if (!(err instanceof ToolFailure)) throw err;
|
|
1372
1384
|
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1385
|
+
// Bad arguments are the model talking to itself. "old_string and new_string
|
|
1386
|
+
// are identical" is a correction it will make on the next step, and it means
|
|
1387
|
+
// nothing to whoever is watching except that something went wrong. It goes
|
|
1388
|
+
// to the model, which can act on it, and not to the screen. A refusal the
|
|
1389
|
+
// user made, and anything that actually failed, still shows.
|
|
1390
|
+
if (err instanceof Declined) this.ui.toolFailed('declined');
|
|
1391
|
+
else if (err.kind !== 'bad_args') this.ui.toolFailed(`${err.kind}: ${err.failed}`);
|
|
1376
1392
|
this.push({
|
|
1377
1393
|
role: 'tool',
|
|
1378
1394
|
toolCallId: call.id,
|
package/src/ui/screen.js
CHANGED
|
@@ -297,40 +297,66 @@ export class Screen {
|
|
|
297
297
|
// U+25CF, not U+23FA: the latter carries emoji presentation, which Windows
|
|
298
298
|
// Terminal draws as a white circle on a blue tile.
|
|
299
299
|
const kind = groupKind(label);
|
|
300
|
-
|
|
301
|
-
//
|
|
302
|
-
//
|
|
303
|
-
//
|
|
304
|
-
const
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
// rather than adding another almost-identical one beneath it.
|
|
308
|
-
if (run && run.kind === kind && gap <= 1) {
|
|
309
|
-
if (gap === 1) this.lines.pop(); // its single result line, now counted
|
|
300
|
+
// One line per kind of work for as long as the model is working on one
|
|
301
|
+
// thing. Reading, writing and reading again used to draw six lines that
|
|
302
|
+
// said three things; now the "Reading files" line it already has is the
|
|
303
|
+
// one that counts up, wherever it sits.
|
|
304
|
+
const run = (this.segment ??= new Map()).get(kind);
|
|
305
|
+
|
|
306
|
+
if (run && this.lines[run.at] !== undefined) {
|
|
310
307
|
run.count++;
|
|
311
308
|
run.label = label;
|
|
312
309
|
run.targets.push(groupTarget(label));
|
|
313
|
-
this.
|
|
310
|
+
this.run = run;
|
|
311
|
+
this.paintRun({ live: true });
|
|
314
312
|
} else {
|
|
315
313
|
this.push(`${narrationMark()} ${narration(asLabel(label))}`);
|
|
316
314
|
this.run = {
|
|
317
315
|
kind, count: 1, at: this.lines.length - 1, label,
|
|
318
316
|
targets: [groupTarget(label)], added: 0, removed: 0,
|
|
319
317
|
};
|
|
318
|
+
this.segment.set(kind, this.run);
|
|
319
|
+
this.paintRun({ live: true });
|
|
320
320
|
}
|
|
321
321
|
this.updateSpinner(label);
|
|
322
322
|
}
|
|
323
323
|
|
|
324
|
-
/**
|
|
325
|
-
|
|
324
|
+
/**
|
|
325
|
+
* The model speaking — or a plan, or a failure — ends the segment.
|
|
326
|
+
*
|
|
327
|
+
* Up to that point a kind of work keeps one line and counts up on it. After
|
|
328
|
+
* it, the next read is a new piece of work and deserves its own line, which
|
|
329
|
+
* is what makes the transcript read as a sequence of things done rather
|
|
330
|
+
* than a set of running totals.
|
|
331
|
+
*/
|
|
332
|
+
endRun() {
|
|
333
|
+
if (this.run) this.paintRun({ live: false });
|
|
334
|
+
this.run = null;
|
|
335
|
+
this.segment = new Map();
|
|
336
|
+
}
|
|
326
337
|
|
|
327
338
|
/** Redraw the run's single line from what it has accumulated. */
|
|
328
|
-
|
|
339
|
+
/**
|
|
340
|
+
* Redraw the run's single line from what it has accumulated.
|
|
341
|
+
*
|
|
342
|
+
* While its step is still running the text shimmers, which is the only
|
|
343
|
+
* thing on screen saying "this is happening now" once the per-step result
|
|
344
|
+
* lines are gone. It settles to plain dim the moment the step finishes, so
|
|
345
|
+
* the finished ones above stay quiet.
|
|
346
|
+
*/
|
|
347
|
+
paintRun({ live = this.run?.live } = {}) {
|
|
329
348
|
if (!this.run) return;
|
|
330
|
-
|
|
349
|
+
const text = asLabel(runLine(this.run));
|
|
350
|
+
this.run.live = live;
|
|
351
|
+
this.lines[this.run.at] = `${narrationMark()} ${live ? shimmer(text, this.tick * FRAME_MS) : narration(text)}`;
|
|
331
352
|
this.render();
|
|
332
353
|
}
|
|
333
354
|
|
|
355
|
+
/** Let the line in flight animate, one frame per tick. */
|
|
356
|
+
paintLiveRun() {
|
|
357
|
+
if (this.run?.live && this.lines[this.run.at] !== undefined) this.paintRun({ live: true });
|
|
358
|
+
}
|
|
359
|
+
|
|
334
360
|
/**
|
|
335
361
|
* A change, as its two numbers.
|
|
336
362
|
*
|
|
@@ -361,7 +387,10 @@ export class Screen {
|
|
|
361
387
|
* already names the step, and a change adds its numbers to that same line.
|
|
362
388
|
* Only a failure earns a line of its own.
|
|
363
389
|
*/
|
|
364
|
-
toolResult() {
|
|
390
|
+
toolResult() {
|
|
391
|
+
// The step is over: the line stops moving and joins the quiet ones above.
|
|
392
|
+
if (this.run) this.paintRun({ live: false });
|
|
393
|
+
}
|
|
365
394
|
|
|
366
395
|
toolFailed(summary) {
|
|
367
396
|
// A failure is never folded away.
|
|
@@ -856,6 +885,7 @@ export class Screen {
|
|
|
856
885
|
if (this.spinTimer) return;
|
|
857
886
|
this.spinTimer = setInterval(() => {
|
|
858
887
|
this.tick++;
|
|
888
|
+
this.paintLiveRun();
|
|
859
889
|
this.paintStatus();
|
|
860
890
|
}, FRAME_MS);
|
|
861
891
|
this.spinTimer.unref?.();
|