@yolo-labs/yolobridge 0.12.0 → 0.13.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/dist/api-client.js +2 -0
- package/dist/attach-cmd.js +2 -0
- package/dist/local-agent.js +93 -3
- package/package.json +1 -1
package/dist/api-client.js
CHANGED
|
@@ -199,6 +199,7 @@ export async function postReadOutputReply(cfg, workspaceId, attachmentId, reques
|
|
|
199
199
|
output,
|
|
200
200
|
busy,
|
|
201
201
|
...(extra?.cols && extra?.rows ? { cols: extra.cols, rows: extra.rows } : {}),
|
|
202
|
+
...(extra?.usedRows ? { usedRows: extra.usedRows } : {}),
|
|
202
203
|
...(extra?.raw
|
|
203
204
|
? {
|
|
204
205
|
raw: extra.raw.data,
|
|
@@ -245,6 +246,7 @@ export async function postOutputChunk(cfg, workspaceId, attachmentId, chunk) {
|
|
|
245
246
|
...(chunk.epoch ? { epoch: chunk.epoch } : {}),
|
|
246
247
|
...(typeof chunk.startOffset === 'number' ? { startOffset: chunk.startOffset } : {}),
|
|
247
248
|
...(chunk.cols && chunk.rows ? { cols: chunk.cols, rows: chunk.rows } : {}),
|
|
249
|
+
...(chunk.usedRows ? { usedRows: chunk.usedRows } : {}),
|
|
248
250
|
});
|
|
249
251
|
return Boolean(body?.relayed);
|
|
250
252
|
}
|
package/dist/attach-cmd.js
CHANGED
|
@@ -642,6 +642,7 @@ export async function runAttachDaemon(deps) {
|
|
|
642
642
|
startOffset: batch.startOffset,
|
|
643
643
|
cols: geometry?.cols,
|
|
644
644
|
rows: geometry?.rows,
|
|
645
|
+
usedRows: geometry?.usedRows,
|
|
645
646
|
});
|
|
646
647
|
if (outputStream === session)
|
|
647
648
|
session.seq += 1;
|
|
@@ -875,6 +876,7 @@ export async function runAttachDaemon(deps) {
|
|
|
875
876
|
.postReadOutputReply(scopedCfg(), workspaceId, attachmentId, action.requestId, captured.output, captured.busy, {
|
|
876
877
|
cols: raw?.cols ?? captured.cols,
|
|
877
878
|
rows: raw?.rows ?? captured.rows,
|
|
879
|
+
usedRows: raw?.usedRows ?? captured.usedRows,
|
|
878
880
|
raw: raw
|
|
879
881
|
? {
|
|
880
882
|
epoch: raw.epoch,
|
package/dist/local-agent.js
CHANGED
|
@@ -294,6 +294,7 @@ export function takeRawSeed() {
|
|
|
294
294
|
data,
|
|
295
295
|
cols: current.cols,
|
|
296
296
|
rows: current.rows,
|
|
297
|
+
usedRows: computeUsedRows(current.term),
|
|
297
298
|
truncated,
|
|
298
299
|
// Only a truncated replay is missing state that predates it.
|
|
299
300
|
...(truncated ? { prologue: buildModePrologue(observedModes(current.term), rawRing.modes) } : {}),
|
|
@@ -337,12 +338,93 @@ export function primeRawStream(maxBytes = RAW_STREAM_PRIME_BYTES) {
|
|
|
337
338
|
: all;
|
|
338
339
|
return { epoch: rawRing.epoch, startOffset: groundStart, data };
|
|
339
340
|
}
|
|
341
|
+
/**
|
|
342
|
+
* Is this cell blank — nothing a viewer could see in it?
|
|
343
|
+
*
|
|
344
|
+
* "Blank" is deliberately the SAME notion `serializeTerminalBuffer`'s
|
|
345
|
+
* right-trim uses: an unwritten or space cell whose PAINT is also default. A
|
|
346
|
+
* run of spaces carrying a background colour (a status bar, a selection, a
|
|
347
|
+
* progress bar's filled portion) is real, visible output, and calling it blank
|
|
348
|
+
* would crop it off the bottom of the screen.
|
|
349
|
+
*/
|
|
350
|
+
function cellIsBlank(cell) {
|
|
351
|
+
const chars = cell.getChars();
|
|
352
|
+
if (chars !== '' && chars !== ' ')
|
|
353
|
+
return false;
|
|
354
|
+
return (!!cell.isBgDefault() && !cell.isInverse() && !cell.isUnderline() && !cell.isStrikethrough());
|
|
355
|
+
}
|
|
356
|
+
/**
|
|
357
|
+
* How many of the grid's `rows` the session is actually USING — the last row
|
|
358
|
+
* with anything visible on it (or the cursor's row), whichever is lower down.
|
|
359
|
+
*
|
|
360
|
+
* ⚠️ WHY THE DAEMON COMPUTES THIS AND NOT THE VIEWER. The tile only ever
|
|
361
|
+
* receives a byte stream; reconstructing "how much of the screen is in use"
|
|
362
|
+
* from it means re-implementing the parser's own bookkeeping. The daemon
|
|
363
|
+
* already holds the authoritative `@xterm/headless` buffer, so the answer is a
|
|
364
|
+
* read rather than an inference.
|
|
365
|
+
*
|
|
366
|
+
* ⚠️ ALTERNATE SCREEN ⇒ `rows`, ALWAYS. A full-screen TUI legitimately paints
|
|
367
|
+
* every row it was given, and plenty of those rows look blank by this
|
|
368
|
+
* predicate (a padded pane, an empty list body, the gap above a footer).
|
|
369
|
+
* Cropping there would clip live UI off the bottom of a viewer's tile, so the
|
|
370
|
+
* alternate screen simply opts out: the whole grid is in use by definition.
|
|
371
|
+
*
|
|
372
|
+
* COST: bounded by the number of TRAILING BLANK rows — the walk starts at the
|
|
373
|
+
* bottom and returns at the first row with content, so a busy screen costs one
|
|
374
|
+
* row's cells and a mostly-empty one costs `(rows − usedRows) × cols` cell
|
|
375
|
+
* reads with no allocation. Nothing is serialized: this is called on every
|
|
376
|
+
* relayed chunk and a screen dump per chunk would be exactly the cost the raw
|
|
377
|
+
* stream exists to avoid.
|
|
378
|
+
*
|
|
379
|
+
* Never returns 0 — a terminal is always showing at least one row, and a zero
|
|
380
|
+
* would divide straight through the viewer's scale computation.
|
|
381
|
+
*/
|
|
382
|
+
export function computeUsedRows(term) {
|
|
383
|
+
const buffer = term.buffer.active;
|
|
384
|
+
const rows = Math.max(1, term.rows);
|
|
385
|
+
if (buffer.type === 'alternate')
|
|
386
|
+
return rows;
|
|
387
|
+
// The viewport's first line within the buffer. The daemon's terminal is
|
|
388
|
+
// never scrolled by a human (nobody scrolls it — it is fed and read
|
|
389
|
+
// programmatically), so the base IS the viewport.
|
|
390
|
+
const top = buffer.baseY;
|
|
391
|
+
// The cursor is the session's live edge even on a row that is still empty
|
|
392
|
+
// (a fresh prompt line, a cleared input box), so it is a floor.
|
|
393
|
+
let used = Math.min(rows, Math.max(1, buffer.cursorY + 1));
|
|
394
|
+
for (let y = rows - 1; y >= used; y--) {
|
|
395
|
+
const line = buffer.getLine(top + y);
|
|
396
|
+
if (!line)
|
|
397
|
+
continue;
|
|
398
|
+
let blank = true;
|
|
399
|
+
for (let x = 0; x < line.length; x++) {
|
|
400
|
+
const cell = line.getCell(x);
|
|
401
|
+
if (!cell)
|
|
402
|
+
continue;
|
|
403
|
+
if (!cellIsBlank(cell)) {
|
|
404
|
+
blank = false;
|
|
405
|
+
break;
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
if (!blank) {
|
|
409
|
+
used = y + 1;
|
|
410
|
+
break;
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
return used;
|
|
414
|
+
}
|
|
340
415
|
/** The PTY's grid, which the tile must render at EXACTLY (it cannot be
|
|
341
|
-
* resized — the human at the keyboard is watching the same PTY)
|
|
416
|
+
* resized — the human at the keyboard is watching the same PTY), plus how
|
|
417
|
+
* much of that grid is in USE.
|
|
418
|
+
*
|
|
419
|
+
* ⚠️ `usedRows` IS NOT A SECOND GEOMETRY. It never changes `cols`/`rows` and
|
|
420
|
+
* the viewer never resizes its terminal to it: a viewer's terminal that is
|
|
421
|
+
* not exactly `cols`×`rows` cannot replay the daemon's bytes (relative cursor
|
|
422
|
+
* moves and the scroll region are defined against the real grid). It exists
|
|
423
|
+
* so the viewer can decide how large to draw the text. */
|
|
342
424
|
export function getLocalAgentGeometry() {
|
|
343
425
|
if (!current)
|
|
344
426
|
return undefined;
|
|
345
|
-
return { cols: current.cols, rows: current.rows };
|
|
427
|
+
return { cols: current.cols, rows: current.rows, usedRows: computeUsedRows(current.term) };
|
|
346
428
|
}
|
|
347
429
|
/** The same recent-activity heuristic `captureLocalAgentOutput` reports, without
|
|
348
430
|
* paying for a buffer serialization. */
|
|
@@ -872,5 +954,13 @@ export async function captureLocalAgentOutput() {
|
|
|
872
954
|
// The grid the screen above was laid out FOR. A viewer that renders it at
|
|
873
955
|
// any other width re-wraps every long line — the dump has no width of its
|
|
874
956
|
// own, only the one the PTY composed it at.
|
|
875
|
-
return {
|
|
957
|
+
return {
|
|
958
|
+
output,
|
|
959
|
+
busy,
|
|
960
|
+
cols: current.cols,
|
|
961
|
+
rows: current.rows,
|
|
962
|
+
// How much of that grid is in use, so the viewer can scale the text to the
|
|
963
|
+
// rows that carry content instead of to a screenful of blank tail.
|
|
964
|
+
usedRows: computeUsedRows(current.term),
|
|
965
|
+
};
|
|
876
966
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yolo-labs/yolobridge",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.0",
|
|
4
4
|
"description": "YoloBridge — local coding-agent daemon that attaches a user's own Claude Code/Codex session to a YOLO Studio workspace as a first-class tile (docs/YOLOBRIDGE_PLAN.md, build-order Phase 5).",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|