@yolo-labs/yolobridge 0.8.0 → 0.9.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/local-agent.js +164 -9
- package/package.json +1 -1
package/dist/local-agent.js
CHANGED
|
@@ -124,22 +124,177 @@ function resolveRows(opts) {
|
|
|
124
124
|
return DEFAULT_ROWS;
|
|
125
125
|
return process.stdout.rows || DEFAULT_ROWS;
|
|
126
126
|
}
|
|
127
|
+
const DEFAULT_ATTRS = {
|
|
128
|
+
bold: false,
|
|
129
|
+
dim: false,
|
|
130
|
+
italic: false,
|
|
131
|
+
underline: false,
|
|
132
|
+
inverse: false,
|
|
133
|
+
strikethrough: false,
|
|
134
|
+
fgMode: 'default',
|
|
135
|
+
fgColor: 0,
|
|
136
|
+
bgMode: 'default',
|
|
137
|
+
bgColor: 0,
|
|
138
|
+
};
|
|
139
|
+
function attrsAreDefault(a) {
|
|
140
|
+
return (!a.bold &&
|
|
141
|
+
!a.dim &&
|
|
142
|
+
!a.italic &&
|
|
143
|
+
!a.underline &&
|
|
144
|
+
!a.inverse &&
|
|
145
|
+
!a.strikethrough &&
|
|
146
|
+
a.fgMode === 'default' &&
|
|
147
|
+
a.bgMode === 'default');
|
|
148
|
+
}
|
|
149
|
+
/** `38;…` / `48;…` (or the compact 30-37/90-97/40-47/100-107 forms). */
|
|
150
|
+
function colorCodes(mode, color, fg) {
|
|
151
|
+
if (mode === 'default')
|
|
152
|
+
return [fg ? '39' : '49'];
|
|
153
|
+
if (mode === 'rgb') {
|
|
154
|
+
// Typings: RGB mode packs the colour as 0xRRGGBB.
|
|
155
|
+
const r = (color >> 16) & 0xff;
|
|
156
|
+
const g = (color >> 8) & 0xff;
|
|
157
|
+
const b = color & 0xff;
|
|
158
|
+
return [`${fg ? 38 : 48};2;${r};${g};${b}`];
|
|
159
|
+
}
|
|
160
|
+
// Palette: 0-255. 0-7 and 8-15 have compact single-code forms; the rest
|
|
161
|
+
// need the indexed form.
|
|
162
|
+
if (color < 8)
|
|
163
|
+
return [String((fg ? 30 : 40) + color)];
|
|
164
|
+
if (color < 16)
|
|
165
|
+
return [String((fg ? 90 : 100) + (color - 8))];
|
|
166
|
+
return [`${fg ? 38 : 48};5;${color}`];
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* The SGR parameters that move `prev` to `next` — EMPTY when nothing
|
|
170
|
+
* changed, which is what keeps the payload small (see
|
|
171
|
+
* `serializeTerminalBuffer`).
|
|
172
|
+
*/
|
|
173
|
+
function sgrDiff(prev, next) {
|
|
174
|
+
const codes = [];
|
|
175
|
+
// Bold and dim share one "off" code (22), so turning either off means
|
|
176
|
+
// re-asserting whichever of the two survives.
|
|
177
|
+
if ((prev.bold && !next.bold) || (prev.dim && !next.dim)) {
|
|
178
|
+
codes.push('22');
|
|
179
|
+
if (next.bold)
|
|
180
|
+
codes.push('1');
|
|
181
|
+
if (next.dim)
|
|
182
|
+
codes.push('2');
|
|
183
|
+
}
|
|
184
|
+
else {
|
|
185
|
+
if (!prev.bold && next.bold)
|
|
186
|
+
codes.push('1');
|
|
187
|
+
if (!prev.dim && next.dim)
|
|
188
|
+
codes.push('2');
|
|
189
|
+
}
|
|
190
|
+
if (prev.italic !== next.italic)
|
|
191
|
+
codes.push(next.italic ? '3' : '23');
|
|
192
|
+
if (prev.underline !== next.underline)
|
|
193
|
+
codes.push(next.underline ? '4' : '24');
|
|
194
|
+
if (prev.inverse !== next.inverse)
|
|
195
|
+
codes.push(next.inverse ? '7' : '27');
|
|
196
|
+
if (prev.strikethrough !== next.strikethrough)
|
|
197
|
+
codes.push(next.strikethrough ? '9' : '29');
|
|
198
|
+
if (prev.fgMode !== next.fgMode || prev.fgColor !== next.fgColor) {
|
|
199
|
+
codes.push(...colorCodes(next.fgMode, next.fgColor, true));
|
|
200
|
+
}
|
|
201
|
+
if (prev.bgMode !== next.bgMode || prev.bgColor !== next.bgColor) {
|
|
202
|
+
codes.push(...colorCodes(next.bgMode, next.bgColor, false));
|
|
203
|
+
}
|
|
204
|
+
return codes;
|
|
205
|
+
}
|
|
127
206
|
/**
|
|
128
|
-
* Serializes the terminal's current buffer (scrollback + viewport) to
|
|
129
|
-
*
|
|
130
|
-
*
|
|
131
|
-
*
|
|
132
|
-
*
|
|
133
|
-
* —
|
|
134
|
-
*
|
|
135
|
-
*
|
|
207
|
+
* Serializes the terminal's current buffer (scrollback + viewport) to text
|
|
208
|
+
* that keeps the agent's COLOUR and text styling, as SGR escapes only.
|
|
209
|
+
*
|
|
210
|
+
* Still deliberately not `@xterm/addon-serialize`: that addon reconstructs
|
|
211
|
+
* a fully VT100-replayable stream — cursor moves, scroll regions, mode
|
|
212
|
+
* switches — for re-feeding into another terminal, which is the wrong
|
|
213
|
+
* shape for `read_tile_output`. Its consumers (the browser tile, and an
|
|
214
|
+
* orchestrator/LLM reading the same capture) want the SCREEN as lines,
|
|
215
|
+
* with the styling that makes an agent's output readable, and nothing
|
|
216
|
+
* that repositions a cursor. So we walk `buffer.active` cell by cell and
|
|
217
|
+
* re-emit just the SGR state.
|
|
218
|
+
*
|
|
219
|
+
* Payload discipline is the reason this walks cells rather than emitting
|
|
220
|
+
* per cell: an escape is written ONLY where the attribute state actually
|
|
221
|
+
* changes, so a screen of unstyled text emits ZERO escapes and is
|
|
222
|
+
* byte-identical to what the old `translateToString(true)` produced. That
|
|
223
|
+
* matters — this capture is polled on an interval and crosses the
|
|
224
|
+
* network on every poll.
|
|
225
|
+
*
|
|
226
|
+
* Each line is self-contained: any line that ends with non-default
|
|
227
|
+
* attributes is closed with a reset, so state cannot bleed into the next
|
|
228
|
+
* line (the webapp splits this on `\n` and renders lines independently).
|
|
229
|
+
*
|
|
230
|
+
* NOT preserved, by design: cursor position, the alternate-screen flag,
|
|
231
|
+
* scroll regions, hyperlinks (OSC 8), and blink/invisible/overline — none
|
|
232
|
+
* of them survive into a static, line-split view.
|
|
136
233
|
*/
|
|
137
234
|
export function serializeTerminalBuffer(term) {
|
|
138
235
|
const buffer = term.buffer.active;
|
|
139
236
|
const lines = [];
|
|
140
237
|
for (let i = 0; i < buffer.length; i++) {
|
|
141
238
|
const line = buffer.getLine(i);
|
|
142
|
-
|
|
239
|
+
if (!line) {
|
|
240
|
+
lines.push('');
|
|
241
|
+
continue;
|
|
242
|
+
}
|
|
243
|
+
// Collect first, so trailing blanks can be trimmed before any escape
|
|
244
|
+
// is emitted for them (matching the old `translateToString(true)`).
|
|
245
|
+
const cells = [];
|
|
246
|
+
for (let x = 0; x < line.length; x++) {
|
|
247
|
+
const cell = line.getCell(x);
|
|
248
|
+
if (!cell)
|
|
249
|
+
continue;
|
|
250
|
+
// Width 0 = the right half of a wide (CJK/emoji) glyph; its content
|
|
251
|
+
// already came out of the width-2 cell before it. Emitting it too
|
|
252
|
+
// would duplicate the character.
|
|
253
|
+
if (cell.getWidth() === 0)
|
|
254
|
+
continue;
|
|
255
|
+
// An untouched cell has no content at all; it renders as a space.
|
|
256
|
+
const chars = cell.getChars();
|
|
257
|
+
cells.push({
|
|
258
|
+
text: chars === '' ? ' ' : chars,
|
|
259
|
+
attrs: {
|
|
260
|
+
bold: !!cell.isBold(),
|
|
261
|
+
dim: !!cell.isDim(),
|
|
262
|
+
italic: !!cell.isItalic(),
|
|
263
|
+
underline: !!cell.isUnderline(),
|
|
264
|
+
inverse: !!cell.isInverse(),
|
|
265
|
+
strikethrough: !!cell.isStrikethrough(),
|
|
266
|
+
fgMode: cell.isFgRGB() ? 'rgb' : cell.isFgPalette() ? 'palette' : 'default',
|
|
267
|
+
// In default mode the colour NUMBER is meaningless (the typings
|
|
268
|
+
// say "should be 0"; the runtime actually reports -1). Normalise
|
|
269
|
+
// it so two default cells compare equal and emit no escape.
|
|
270
|
+
fgColor: cell.isFgDefault() ? 0 : cell.getFgColor(),
|
|
271
|
+
bgMode: cell.isBgRGB() ? 'rgb' : cell.isBgPalette() ? 'palette' : 'default',
|
|
272
|
+
bgColor: cell.isBgDefault() ? 0 : cell.getBgColor(),
|
|
273
|
+
},
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
// Right-trim, as before — but only cells that are blank AND unstyled.
|
|
277
|
+
// A run of spaces carrying a background colour is real, visible output
|
|
278
|
+
// (a status bar, a selection); dropping it would lose the paint.
|
|
279
|
+
while (cells.length > 0) {
|
|
280
|
+
const last = cells[cells.length - 1];
|
|
281
|
+
if (last.text === ' ' && attrsAreDefault(last.attrs))
|
|
282
|
+
cells.pop();
|
|
283
|
+
else
|
|
284
|
+
break;
|
|
285
|
+
}
|
|
286
|
+
let out = '';
|
|
287
|
+
let state = DEFAULT_ATTRS;
|
|
288
|
+
for (const cell of cells) {
|
|
289
|
+
const codes = sgrDiff(state, cell.attrs);
|
|
290
|
+
if (codes.length > 0)
|
|
291
|
+
out += `\x1b[${codes.join(';')}m`;
|
|
292
|
+
out += cell.text;
|
|
293
|
+
state = cell.attrs;
|
|
294
|
+
}
|
|
295
|
+
if (!attrsAreDefault(state))
|
|
296
|
+
out += '\x1b[0m';
|
|
297
|
+
lines.push(out);
|
|
143
298
|
}
|
|
144
299
|
while (lines.length > 0 && lines[lines.length - 1] === '')
|
|
145
300
|
lines.pop();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yolo-labs/yolobridge",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.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",
|