repoburg 1.3.269 → 1.3.271
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/agent-pane/backend/dist/index.js +54904 -277
- package/agent-pane/backend/dist/server.js +54904 -277
- package/agent-pane/examples/pr-review/manifest.json +1 -1
- package/agent-pane/frontend/dist/assets/index-DSVgGDLh.css +1 -0
- package/agent-pane/frontend/dist/assets/index-h9tBvm8B.js +606 -0
- package/agent-pane/frontend/dist/index.html +2 -2
- package/daemon/dist/tabtape/config.js +1 -1
- package/daemon/dist/tabtape/mcp/definitions.js +2 -2
- package/daemon/dist/tabtape/mcp/server.js +2 -2
- package/daemon/dist/tabtape/mcp/server.spec.js +18 -0
- package/daemon/dist/tabtape/mcp/tools.js +10 -0
- package/daemon/dist/tabtape/query/perf.js +53 -1
- package/daemon/dist/tabtape/query/query.spec.js +13 -1
- package/daemon/dist/tabtape/query/types.js +4 -0
- package/daemon/dist/tabtape/testing/querytest.js +8 -1
- package/package.json +4 -1
- package/tabtape/extension/dist/build-info.json +4 -4
- package/tabtape/extension/dist/manifest.json +2 -2
- package/tabtape/extension/dist/recorder.js +21 -21
- package/tabtape/extension/dist/recorder.js.map +1 -1
- package/tabtape/ui/dist/app/assets/{index-iWNSSrAE.js → index-B7yWGVQ_.js} +10 -9
- package/tabtape/ui/dist/app/assets/{index-Canv3Ljk.css → index-BE22UToa.css} +1 -1
- package/tabtape/ui/dist/app/index.html +2 -2
- package/tabtape/ui/dist/build-info.json +4 -4
- package/tabtape/ui/dist/export/viewer.css +1 -1
- package/tabtape/ui/dist/export/viewer.js +6 -5
- package/agent-pane/frontend/dist/assets/index-BaBO6I13.js +0 -567
- package/agent-pane/frontend/dist/assets/index-K4hE7q_n.css +0 -1
|
@@ -10,8 +10,8 @@
|
|
|
10
10
|
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;450;510;590;600&family=JetBrains+Mono:wght@400;500&display=swap"
|
|
11
11
|
rel="stylesheet"
|
|
12
12
|
/>
|
|
13
|
-
<script type="module" crossorigin src="/assets/index-
|
|
14
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
13
|
+
<script type="module" crossorigin src="/assets/index-h9tBvm8B.js"></script>
|
|
14
|
+
<link rel="stylesheet" crossorigin href="/assets/index-DSVgGDLh.css">
|
|
15
15
|
</head>
|
|
16
16
|
<body>
|
|
17
17
|
<div id="root"></div>
|
|
@@ -12,7 +12,7 @@ const os_1 = __importDefault(require("os"));
|
|
|
12
12
|
const path_1 = __importDefault(require("path"));
|
|
13
13
|
const redact_1 = require("./redact");
|
|
14
14
|
/** Version of the tabtape component (extension, replay UI, server API). */
|
|
15
|
-
exports.TABTAPE_VERSION = '0.
|
|
15
|
+
exports.TABTAPE_VERSION = '0.6.0';
|
|
16
16
|
/** URL prefix of everything tabtape serves on the daemon's port. */
|
|
17
17
|
exports.TABTAPE_MOUNT = '/tabtape';
|
|
18
18
|
const UNITS = [
|
|
@@ -135,7 +135,7 @@ const TOOLS = [
|
|
|
135
135
|
{ name: 'has_errors', type: 'boolean', description: 'only sessions with console errors or failed requests' },
|
|
136
136
|
{ name: 'limit', type: 'integer', description: 'maximum number of sessions (default 20)' },
|
|
137
137
|
], OUTPUT.list, (t, input) => t.listSessions(input)),
|
|
138
|
-
def('get_session_summary', "What happened in a recording, in order: page views, clicks, form changes, submits, the app's own events (tabtape.track), console errors, failed requests, poor Web Vitals and long freezes, each with its time and replay link. Also the identified user (tabtape.identify).", [
|
|
138
|
+
def('get_session_summary', "What happened in a recording, in order: page views, clicks, form changes, submits, the app's own events (tabtape.track), console errors, failed requests, poor Web Vitals and long freezes, each with its time and replay link. Also the identified user (tabtape.identify) and the page's memory: JS heap at the start, at the end and its peak.", [
|
|
139
139
|
{ name: 'session_id', type: 'string', description: 'session id, or latest for the most recent recording', required: true },
|
|
140
140
|
{ name: 'cursor', type: 'string', description: CURSOR },
|
|
141
141
|
], OUTPUT.summary, (t, input) => t.getSessionSummary(input)),
|
|
@@ -174,7 +174,7 @@ const TOOLS = [
|
|
|
174
174
|
{ name: 'limit', type: 'integer', description: 'maximum number of messages (default 100)' },
|
|
175
175
|
{ name: 'cursor', type: 'string', description: CURSOR },
|
|
176
176
|
], OUTPUT.console, (t, input) => t.getConsole(input)),
|
|
177
|
-
def('get_context_at',
|
|
177
|
+
def('get_context_at', "Everything around one moment, in order: user actions, requests and console messages from 10 seconds before to 2 seconds after (adjustable), the page's JS heap at the moment and how it changed in the window, then the last 10 Redux / Zustand store actions before the moment with the state paths they changed. Use it to see what led to an error.", [
|
|
178
178
|
{ name: 'session_id', type: 'string', description: SESSION_LATEST, required: true },
|
|
179
179
|
{
|
|
180
180
|
name: 'at',
|
|
@@ -24,13 +24,13 @@ const log_1 = require("../log");
|
|
|
24
24
|
const definitions_1 = require("./definitions");
|
|
25
25
|
const gofmt_1 = require("./gofmt");
|
|
26
26
|
const tools_1 = require("./tools");
|
|
27
|
-
exports.INSTRUCTIONS = `tabtape records browser sessions locally: DOM replay, clicks, page views, form changes, network requests, console output, the app's own events (tabtape.track) and user id (tabtape.identify), Web Vitals and
|
|
27
|
+
exports.INSTRUCTIONS = `tabtape records browser sessions locally: DOM replay, clicks, page views, form changes, network requests, console output, the app's own events (tabtape.track) and user id (tabtape.identify), Web Vitals, long freezes and the page's memory (JS heap), and Redux / Zustand store actions.
|
|
28
28
|
Use these tools to answer questions about what happened in a recording. Times are offsets from the start of the replay (mm:ss.mmm).
|
|
29
29
|
Every session and line carries a replay link; include the relevant links in answers so the user can watch the moment.
|
|
30
30
|
Start with list_sessions or get_session_summary (session_id "latest" is the most recent recording), then drill down with get_network, get_request, get_console and get_context_at.
|
|
31
31
|
Request and response bodies are only returned by get_request or with include_bodies=true. Sensitive headers are shown as [redacted].
|
|
32
32
|
Recordings made in deep mode also have the real request headers and cookies, the timing split, why a request failed (CORS, blocked cookies, mixed content) and WebSocket connections: get_network with kind=websocket lists them, get_request on a connection pages its messages.
|
|
33
|
-
get_context_at ends with the last store actions before
|
|
33
|
+
get_session_summary says how the JS heap grew over the recording; get_context_at gives the heap at the moment and ends with the last store actions before it and the state paths they changed.
|
|
34
34
|
To record a browser you drive yourself (Playwright MCP, a Playwright script), get_record_link returns links that start and stop a recording in it; open the start link before the check and the stop link after it.`;
|
|
35
35
|
const RESOURCE_PREFIX = 'tabtape://session/';
|
|
36
36
|
const SESSION_TEMPLATE = {
|
|
@@ -501,6 +501,24 @@ describe('TabtapeMcp', () => {
|
|
|
501
501
|
({ text } = await call(e, 'get_context_at', { session_id: 's1', at: '00:03.000' }));
|
|
502
502
|
expect(text).toContain('CLS 0.02 (good)');
|
|
503
503
|
});
|
|
504
|
+
it('reports the page memory', async () => {
|
|
505
|
+
const e = await setup();
|
|
506
|
+
qt.append(e.st, 's1', qt.memory(t0 + 100, 12, 120), qt.memory(t0 + 3100, 64, 900), qt.memory(t0 + 6100, 48, 1500));
|
|
507
|
+
let { text } = await call(e, 'get_session_summary', { session_id: 's1' });
|
|
508
|
+
expect(text).toContain('Memory: JS heap 12.0 MB at the start, 48.0 MB at the end, peak 64.0 MB at 00:03.090 (limit 4.00 GB); DOM elements 120 → 1,500. get_context_at shows the heap at a moment.');
|
|
509
|
+
expect(text).not.toContain('perf');
|
|
510
|
+
({ text } = await call(e, 'get_context_at', { session_id: 's1', at: '00:05', before: '5', after: '2' }));
|
|
511
|
+
expect(text).toContain('Memory at 00:05.000: JS heap 64.0 MB used of 128.0 MB (limit 4.00 GB) · 900 DOM elements; 12.0 MB → 48.0 MB in this window (+36.0 MB).');
|
|
512
|
+
({ text } = await call(e, 'get_context_at', { session_id: 's1', at: '00:05', before: '0.5', after: '0.5' }));
|
|
513
|
+
expect(text).toContain('Memory at 00:05.000: JS heap 64.0 MB used of 128.0 MB (limit 4.00 GB) · 900 DOM elements.\n');
|
|
514
|
+
});
|
|
515
|
+
it('leaves memory out of recordings without samples', async () => {
|
|
516
|
+
const e = await setup();
|
|
517
|
+
let { text } = await call(e, 'get_session_summary', { session_id: 's1' });
|
|
518
|
+
expect(text).not.toContain('Memory');
|
|
519
|
+
({ text } = await call(e, 'get_context_at', { session_id: 's1', at: '00:00' }));
|
|
520
|
+
expect(text).not.toContain('Memory');
|
|
521
|
+
});
|
|
504
522
|
it('reports deep mode network data and store state', async () => {
|
|
505
523
|
const e = await setup();
|
|
506
524
|
const recs = [
|
|
@@ -168,6 +168,7 @@ class Tools {
|
|
|
168
168
|
const notes = dataNotes(v);
|
|
169
169
|
if (notes !== '')
|
|
170
170
|
b.write(notes);
|
|
171
|
+
b.write((0, perf_1.memorySummary)(memorySamples(v)));
|
|
171
172
|
if (so.live)
|
|
172
173
|
b.write('Live: the recording is still being written.\n');
|
|
173
174
|
if (so.trimmed)
|
|
@@ -568,6 +569,7 @@ class Tools {
|
|
|
568
569
|
out.items = items;
|
|
569
570
|
else
|
|
570
571
|
b.write('Nothing was recorded in this window apart from screen changes.\n');
|
|
572
|
+
b.write((0, perf_1.memoryAt)(memorySamples(v), at, at - before, at + after));
|
|
571
573
|
const actions = await this.stateActions(v, at);
|
|
572
574
|
if (actions.length > 0) {
|
|
573
575
|
out.stateActions = actions;
|
|
@@ -677,6 +679,14 @@ function dataNotes(v) {
|
|
|
677
679
|
}
|
|
678
680
|
return b;
|
|
679
681
|
}
|
|
682
|
+
/** The page's memory samples, in time order. */
|
|
683
|
+
function memorySamples(v) {
|
|
684
|
+
const out = [];
|
|
685
|
+
for (const l of v.lines)
|
|
686
|
+
if (l.perf && l.perf.kind === 'memory')
|
|
687
|
+
out.push({ offset: v.offset(l.ts), p: l.perf });
|
|
688
|
+
return out;
|
|
689
|
+
}
|
|
680
690
|
function itemLine(it) {
|
|
681
691
|
return `${it.at} ${(0, gofmt_1.padRight)(it.kind, 9)} ${it.text} [${it.id}] ${it.replay}\n`;
|
|
682
692
|
}
|
|
@@ -6,7 +6,9 @@ exports.baseName = baseName;
|
|
|
6
6
|
exports.longestScript = longestScript;
|
|
7
7
|
exports.sortedJSON = sortedJSON;
|
|
8
8
|
exports.propsText = propsText;
|
|
9
|
-
|
|
9
|
+
exports.memorySummary = memorySummary;
|
|
10
|
+
exports.memoryAt = memoryAt;
|
|
11
|
+
/** Web Vitals, long animation frame and memory texts. */
|
|
10
12
|
const format_1 = require("./format");
|
|
11
13
|
/** A metric's value; CLS has no unit. */
|
|
12
14
|
function vitalValue(name, value) {
|
|
@@ -91,3 +93,53 @@ function propsText(props, n) {
|
|
|
91
93
|
const keys = Object.keys(props).sort();
|
|
92
94
|
return (0, format_1.clip)(keys.map((k) => `${k}: ${propValue(props[k])}`).join(', '), n);
|
|
93
95
|
}
|
|
96
|
+
const count = (n) => n.toLocaleString('en-US');
|
|
97
|
+
/**
|
|
98
|
+
* One line about the page's memory over the whole recording, e.g.
|
|
99
|
+
* "Memory: JS heap 12.0 MB at the start, 48.0 MB at the end, peak 64.0 MB at
|
|
100
|
+
* 00:30.000 (limit 4.00 GB); DOM elements 120 → 900." Empty without samples.
|
|
101
|
+
*/
|
|
102
|
+
function memorySummary(samples) {
|
|
103
|
+
if (samples.length === 0)
|
|
104
|
+
return '';
|
|
105
|
+
const first = samples[0];
|
|
106
|
+
const last = samples[samples.length - 1];
|
|
107
|
+
let peak = first;
|
|
108
|
+
for (const s of samples)
|
|
109
|
+
if (s.p.used > peak.p.used)
|
|
110
|
+
peak = s;
|
|
111
|
+
let b = `Memory: JS heap ${(0, format_1.fmtBytes)(first.p.used)} at the start, ${(0, format_1.fmtBytes)(last.p.used)} at the end, peak ${(0, format_1.fmtBytes)(peak.p.used)} at ${(0, format_1.clock)(peak.offset)}`;
|
|
112
|
+
if (peak.p.limit > 0)
|
|
113
|
+
b += ` (limit ${(0, format_1.fmtBytes)(peak.p.limit)})`;
|
|
114
|
+
if (first.p.nodes !== undefined && last.p.nodes !== undefined)
|
|
115
|
+
b += `; DOM elements ${count(first.p.nodes)} → ${count(last.p.nodes)}`;
|
|
116
|
+
return b + '. get_context_at shows the heap at a moment.\n';
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* The memory at offset `at` (the last sample at or before it, else the
|
|
120
|
+
* first) and its change over the samples between from and to. Empty without
|
|
121
|
+
* samples.
|
|
122
|
+
*/
|
|
123
|
+
function memoryAt(samples, at, from, to) {
|
|
124
|
+
if (samples.length === 0)
|
|
125
|
+
return '';
|
|
126
|
+
let now = samples[0];
|
|
127
|
+
for (const s of samples) {
|
|
128
|
+
if (s.offset > at)
|
|
129
|
+
break;
|
|
130
|
+
now = s;
|
|
131
|
+
}
|
|
132
|
+
let b = `Memory at ${(0, format_1.clock)(at)}: JS heap ${(0, format_1.fmtBytes)(now.p.used)} used of ${(0, format_1.fmtBytes)(now.p.total)}`;
|
|
133
|
+
if (now.p.limit > 0)
|
|
134
|
+
b += ` (limit ${(0, format_1.fmtBytes)(now.p.limit)})`;
|
|
135
|
+
if (now.p.nodes !== undefined)
|
|
136
|
+
b += ` · ${count(now.p.nodes)} DOM elements`;
|
|
137
|
+
const window = samples.filter((s) => s.offset >= from && s.offset <= to);
|
|
138
|
+
if (window.length >= 2) {
|
|
139
|
+
const a = window[0].p.used;
|
|
140
|
+
const z = window[window.length - 1].p.used;
|
|
141
|
+
const d = z - a;
|
|
142
|
+
b += `; ${(0, format_1.fmtBytes)(a)} → ${(0, format_1.fmtBytes)(z)} in this window (${d < 0 ? '-' : '+'}${(0, format_1.fmtBytes)(Math.abs(d))})`;
|
|
143
|
+
}
|
|
144
|
+
return b + '.\n';
|
|
145
|
+
}
|
|
@@ -225,9 +225,21 @@ describe('network texts', () => {
|
|
|
225
225
|
});
|
|
226
226
|
describe('perf texts', () => {
|
|
227
227
|
it('names the longest script and renders props', () => {
|
|
228
|
-
const p = { kind: 'loaf', name: '', value: 0, rating: '', id: '', url: '', duration: 600, blockingDuration: 550 };
|
|
228
|
+
const p = { kind: 'loaf', name: '', value: 0, rating: '', id: '', url: '', duration: 600, blockingDuration: 550, used: 0, total: 0, limit: 0 };
|
|
229
229
|
expect((0, perf_1.longestScript)({ ...p, scripts: [{ url: 'http://a.test/js/slow.js?v=1', fn: 'handleClick', invoker: '', invokerType: '', duration: 1 }] })).toBe('slow.js handleClick');
|
|
230
230
|
expect((0, perf_1.longestScript)({ ...p, scripts: [{ url: '', fn: '', invoker: 'BUTTON#go.onclick', invokerType: '', duration: 1 }] })).toBe('BUTTON#go.onclick');
|
|
231
231
|
expect((0, perf_1.propsText)({ plan: 'pro', items: 3, nested: { b: 1, a: 2 }, none: null }, 120)).toBe('items: 3, nested: {"a":2,"b":1}, none: null, plan: pro');
|
|
232
232
|
});
|
|
233
|
+
it('describes memory samples', () => {
|
|
234
|
+
const MB = 1024 * 1024;
|
|
235
|
+
const p = { kind: 'memory', name: '', value: 0, rating: '', id: '', url: '', duration: 0, blockingDuration: 0, limit: 0 };
|
|
236
|
+
const samples = [
|
|
237
|
+
{ offset: 0, p: { ...p, used: 50 * MB, total: 60 * MB } },
|
|
238
|
+
{ offset: 5000, p: { ...p, used: 20 * MB, total: 60 * MB } },
|
|
239
|
+
];
|
|
240
|
+
expect((0, perf_1.memorySummary)([])).toBe('');
|
|
241
|
+
expect((0, perf_1.memoryAt)([], 0, 0, 0)).toBe('');
|
|
242
|
+
expect((0, perf_1.memorySummary)(samples)).toBe('Memory: JS heap 50.0 MB at the start, 20.0 MB at the end, peak 50.0 MB at 00:00.000. get_context_at shows the heap at a moment.\n');
|
|
243
|
+
expect((0, perf_1.memoryAt)(samples, 9000, 0, 9000)).toBe('Memory at 00:09.000: JS heap 20.0 MB used of 60.0 MB; 50.0 MB → 20.0 MB in this window (-30.0 MB).\n');
|
|
244
|
+
});
|
|
233
245
|
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.stateRec = exports.ws = exports.loaf = exports.vital = exports.identify = exports.track = exports.consoleRec = exports.net = exports.submit = exports.change = exports.click = exports.pageView = exports.rrInc = exports.rrFull = exports.rrMeta = exports.UA = exports.T0 = void 0;
|
|
3
|
+
exports.stateRec = exports.ws = exports.memory = exports.loaf = exports.vital = exports.identify = exports.track = exports.consoleRec = exports.net = exports.submit = exports.change = exports.click = exports.pageView = exports.rrInc = exports.rrFull = exports.rrMeta = exports.UA = exports.T0 = void 0;
|
|
4
4
|
exports.testStore = testStore;
|
|
5
5
|
exports.closeTestStores = closeTestStores;
|
|
6
6
|
exports.append = append;
|
|
@@ -108,6 +108,13 @@ const loaf = (ts, duration, ...scripts) => ({
|
|
|
108
108
|
data: { kind: 'loaf', duration, blockingDuration: Math.max(duration - 50, 0), ...(scripts.length ? { scripts } : {}) },
|
|
109
109
|
});
|
|
110
110
|
exports.loaf = loaf;
|
|
111
|
+
/** A memory sample: used and allocated heap in MB, a 4 GB limit. */
|
|
112
|
+
const memory = (ts, usedMb, nodes) => ({
|
|
113
|
+
ts,
|
|
114
|
+
type: 'perf',
|
|
115
|
+
data: { kind: 'memory', used: usedMb * 1024 * 1024, total: 2 * usedMb * 1024 * 1024, limit: 4096 * 1024 * 1024, url: 'http://localhost:7788/', ...(nodes === undefined ? {} : { nodes }) },
|
|
116
|
+
});
|
|
117
|
+
exports.memory = memory;
|
|
111
118
|
/** A WebSocket message of connection conn. */
|
|
112
119
|
const ws = (ts, conn, dir, data) => ({
|
|
113
120
|
ts,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "repoburg",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.271",
|
|
4
4
|
"description": "A local AI-powered software developer assistant that runs on your own machine.",
|
|
5
5
|
"author": "Celal Ertug",
|
|
6
6
|
"license": "SEE LICENSE IN LICENSE",
|
|
@@ -73,6 +73,9 @@
|
|
|
73
73
|
"tabtape:build": "cd tabtape && npm ci --no-audit --no-fund && npm run build",
|
|
74
74
|
"test:publish": "node --test prepublish-check.test.js publish-scope.test.js",
|
|
75
75
|
"dev:tabtape": "cd tabtape && npm run dev -w ui",
|
|
76
|
+
"mac:test": "swift test --package-path daemon-macos",
|
|
77
|
+
"mac:build": "daemon-macos/scripts/build-app.sh",
|
|
78
|
+
"mac:install": "daemon-macos/scripts/install.sh",
|
|
76
79
|
"jaeger:start": "docker run -d --rm --name jaeger -e COLLECTOR_OTLP_ENABLED=true -p 16686:16686 -p 4318:4318 jaegertracing/all-in-one:latest",
|
|
77
80
|
"jaeger:stop": "docker stop jaeger",
|
|
78
81
|
"lo:build": "npm run build -w backend && npm run build -w website2 && (cd agent-pane/backend && npm run build) && (cd agent-pane/frontend && npm run build) && npm run build -w recorder && npm run build -w visual-editor-proxy && npm run tabtape:build && npm run build -w daemon && cd frontend && npm run build",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
|
-
"tree": "
|
|
3
|
-
"commit": "
|
|
2
|
+
"tree": "be7a0bb65dfb8b69e1abe13098765c382867de6a",
|
|
3
|
+
"commit": "72c9df18915c92d3c38bec47b4a34a29d247082a",
|
|
4
4
|
"dirty": false,
|
|
5
|
-
"repoburgVersion": "1.3.
|
|
6
|
-
"builtAt": "2026-10-
|
|
5
|
+
"repoburgVersion": "1.3.271",
|
|
6
|
+
"builtAt": "2026-10-23T23:50:21.067Z"
|
|
7
7
|
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"manifest_version": 3,
|
|
3
3
|
"name": "tabtape recorder",
|
|
4
4
|
"short_name": "tabtape",
|
|
5
|
-
"version": "0.
|
|
5
|
+
"version": "0.6.0",
|
|
6
6
|
"description": "Records the current tab (DOM, clicks, network, console, app state) to tabtape in your local Repoburg daemon for replay.",
|
|
7
7
|
"minimum_chrome_version": "125",
|
|
8
8
|
"icons": {
|
|
@@ -58,5 +58,5 @@
|
|
|
58
58
|
]
|
|
59
59
|
}
|
|
60
60
|
],
|
|
61
|
-
"version_name": "0.
|
|
61
|
+
"version_name": "0.6.0 (repoburg 1.3.271, 72c9df1)"
|
|
62
62
|
}
|