@yusukeshib/pi-babysit 0.6.15 → 0.6.16
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/README.md +5 -0
- package/index.ts +55 -6
- package/log-viewer.ts +27 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -98,6 +98,11 @@ Pi's transcript shortcuts. At the bottom it follows new output; scrolling up
|
|
|
98
98
|
pauses following until you return to the bottom. The viewer resizes with the
|
|
99
99
|
terminal, leaving room for the editor/status/footer. Agent logs separate
|
|
100
100
|
assistant text, tool calls, and results; process logs show terminal output.
|
|
101
|
+
When babysit provides `output.timestamps.jsonl`, process lines begin with a muted
|
|
102
|
+
local `[YYYY-MM-DD HH:mm:ss]` capture time, with wrapped text aligned beneath the
|
|
103
|
+
message. This is the time babysit received the first byte of the line, not an
|
|
104
|
+
application event time. Narrow views place the date above the text. Old logs and
|
|
105
|
+
lines without capture metadata remain readable without invented timestamps.
|
|
101
106
|
Only the selected log is loaded, bounded to the latest 1 MiB / 2,000 display
|
|
102
107
|
lines with an omission notice; the full log file is unchanged. Empty logs show
|
|
103
108
|
a placeholder. Works in the released Pi 1.0 widget host. Mouse interaction
|
package/index.ts
CHANGED
|
@@ -2521,7 +2521,7 @@ export function createSessionWidget(options: {
|
|
|
2521
2521
|
getHeight: () => number;
|
|
2522
2522
|
requestRender: () => void;
|
|
2523
2523
|
getTheme: () => Theme;
|
|
2524
|
-
readLog: (id: string) => { text: string; isAgent: boolean; clipped?: boolean };
|
|
2524
|
+
readLog: (id: string) => { text: string; isAgent: boolean; clipped?: boolean; lineTimestamps?: (number | undefined)[] };
|
|
2525
2525
|
}) {
|
|
2526
2526
|
const listState: WidgetSessionListState = { mode: "running", page: 0 };
|
|
2527
2527
|
let selectedId: string | undefined;
|
|
@@ -2539,6 +2539,7 @@ export function createSessionWidget(options: {
|
|
|
2539
2539
|
log.text,
|
|
2540
2540
|
log.isAgent,
|
|
2541
2541
|
detail.startsWith("task: ") ? detail.slice(6) : detail,
|
|
2542
|
+
log.lineTimestamps,
|
|
2542
2543
|
);
|
|
2543
2544
|
}
|
|
2544
2545
|
function close() {
|
|
@@ -2571,8 +2572,44 @@ export function createSessionWidget(options: {
|
|
|
2571
2572
|
};
|
|
2572
2573
|
}
|
|
2573
2574
|
|
|
2575
|
+
/** Capture times are optional: old logs and incomplete sidecars remain readable. */
|
|
2576
|
+
function readLineTimestamps(file: string, data: Buffer, offset: number): (number | undefined)[] {
|
|
2577
|
+
const metadata = readViewerLog(path.join(path.dirname(file), "output.timestamps.jsonl")).text;
|
|
2578
|
+
const ranges: { start: number; end: number; time: number }[] = [];
|
|
2579
|
+
for (const line of metadata.split("\n")) {
|
|
2580
|
+
try {
|
|
2581
|
+
const record = JSON.parse(line);
|
|
2582
|
+
if (!Array.isArray(record) || record.length !== 3) continue;
|
|
2583
|
+
const [start, length, time] = record;
|
|
2584
|
+
if (!Number.isSafeInteger(start) || start < 0 ||
|
|
2585
|
+
!Number.isSafeInteger(length) || length <= 0 ||
|
|
2586
|
+
!Number.isSafeInteger(start + length) ||
|
|
2587
|
+
!Number.isSafeInteger(time) || time < 0 || time > 8640000000000000 ||
|
|
2588
|
+
start < (ranges.at(-1)?.end ?? 0)) continue;
|
|
2589
|
+
ranges.push({ start, end: start + length, time });
|
|
2590
|
+
} catch { /* A partial or malformed record has no trustworthy time. */ }
|
|
2591
|
+
}
|
|
2592
|
+
let range = 0;
|
|
2593
|
+
const timestamps: (number | undefined)[] = [];
|
|
2594
|
+
const addLine = (position: number) => {
|
|
2595
|
+
const absolute = offset + position;
|
|
2596
|
+
while (range < ranges.length && ranges[range].end <= absolute) range++;
|
|
2597
|
+
const entry = ranges[range];
|
|
2598
|
+
timestamps.push(position < data.length && entry && entry.start <= absolute ? entry.time : undefined);
|
|
2599
|
+
};
|
|
2600
|
+
addLine(0);
|
|
2601
|
+
for (let i = 0; i < data.length; i++) {
|
|
2602
|
+
if (data[i] !== 10 && data[i] !== 13) continue;
|
|
2603
|
+
if (data[i] === 13 && data[i + 1] === 10) i++;
|
|
2604
|
+
addLine(i + 1);
|
|
2605
|
+
}
|
|
2606
|
+
return timestamps;
|
|
2607
|
+
}
|
|
2608
|
+
|
|
2574
2609
|
/** Only the selected session is read; bounded snapshots never affect worker logs. */
|
|
2575
|
-
export function readViewerLog(file: string
|
|
2610
|
+
export function readViewerLog(file: string, withTimestamps = false): {
|
|
2611
|
+
text: string; clipped: boolean; lineTimestamps?: (number | undefined)[];
|
|
2612
|
+
} {
|
|
2576
2613
|
try {
|
|
2577
2614
|
const fd = fs.openSync(file, "r");
|
|
2578
2615
|
try {
|
|
@@ -2580,9 +2617,18 @@ export function readViewerLog(file: string): { text: string; clipped: boolean }
|
|
|
2580
2617
|
const start = Math.max(0, size - 1024 * 1024);
|
|
2581
2618
|
const buffer = Buffer.alloc(size - start);
|
|
2582
2619
|
const bytes = fs.readSync(fd, buffer, 0, buffer.length, start);
|
|
2583
|
-
let
|
|
2584
|
-
|
|
2585
|
-
|
|
2620
|
+
let data = buffer.subarray(0, bytes);
|
|
2621
|
+
let offset = start;
|
|
2622
|
+
if (start > 0) {
|
|
2623
|
+
const skip = data.indexOf(10) + 1;
|
|
2624
|
+
data = data.subarray(skip);
|
|
2625
|
+
offset += skip;
|
|
2626
|
+
}
|
|
2627
|
+
const text = data.toString("utf8");
|
|
2628
|
+
return {
|
|
2629
|
+
text, clipped: start > 0,
|
|
2630
|
+
...(withTimestamps ? { lineTimestamps: readLineTimestamps(file, data, offset) } : {}),
|
|
2631
|
+
};
|
|
2586
2632
|
} finally { fs.closeSync(fd); }
|
|
2587
2633
|
} catch {
|
|
2588
2634
|
return { text: "", clipped: false };
|
|
@@ -3627,7 +3673,10 @@ export default function (pi: ExtensionAPI) {
|
|
|
3627
3673
|
getHeight: () => Math.max(3, tui.terminal.rows - 8),
|
|
3628
3674
|
requestRender: () => tui.requestRender(),
|
|
3629
3675
|
getTheme: () => ctx.ui.theme,
|
|
3630
|
-
readLog: (id) =>
|
|
3676
|
+
readLog: (id) => {
|
|
3677
|
+
const isAgent = kindOf(id) === "subagent";
|
|
3678
|
+
return { ...readViewerLog(logPath(id), !isAgent), isAgent };
|
|
3679
|
+
},
|
|
3631
3680
|
});
|
|
3632
3681
|
return sessionWidget;
|
|
3633
3682
|
}, { placement: "belowEditor" });
|
package/log-viewer.ts
CHANGED
|
@@ -118,11 +118,18 @@ function agentBlocks(raw: string): Block[] {
|
|
|
118
118
|
.map((b) => ({ ...b, text: sanitize(b.text) }));
|
|
119
119
|
}
|
|
120
120
|
|
|
121
|
+
function dateLabel(timestamp: number): string {
|
|
122
|
+
const date = new Date(timestamp);
|
|
123
|
+
const pad = (n: number) => String(n).padStart(2, "0");
|
|
124
|
+
return `[${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}]`;
|
|
125
|
+
}
|
|
126
|
+
|
|
121
127
|
export function createLogViewer(options: LogViewerOptions) {
|
|
122
128
|
let header = "",
|
|
123
129
|
raw = "",
|
|
124
130
|
description = "",
|
|
125
131
|
isAgent = false;
|
|
132
|
+
let lineTimestamps: (number | undefined)[] | undefined;
|
|
126
133
|
let cache: string[] | undefined,
|
|
127
134
|
cacheWidth = -1,
|
|
128
135
|
cacheTheme: Theme | undefined;
|
|
@@ -144,6 +151,22 @@ export function createLogViewer(options: LogViewerOptions) {
|
|
|
144
151
|
]
|
|
145
152
|
: [];
|
|
146
153
|
for (const b of blocks) {
|
|
154
|
+
if (!isAgent && lineTimestamps?.some((time) => time !== undefined)) {
|
|
155
|
+
for (const [index, text] of raw.split(/\r\n|\r|\n/).entries()) {
|
|
156
|
+
const time = lineTimestamps[index];
|
|
157
|
+
const label = time === undefined ? "" : dateLabel(time);
|
|
158
|
+
const indent = label ? visibleWidth(label) + 1 : 0;
|
|
159
|
+
if (indent && width <= indent + 4) {
|
|
160
|
+
lines.push(...new Text(theme.fg("muted", label), 0, 0).render(width));
|
|
161
|
+
lines.push(...new Text(sanitize(text, true), 0, 0).render(width));
|
|
162
|
+
} else {
|
|
163
|
+
const wrapped = new Text(sanitize(text, true), 0, 0).render(width - indent);
|
|
164
|
+
lines.push(...wrapped.map((line, row) =>
|
|
165
|
+
(row === 0 && label ? theme.fg("muted", label) + " " : " ".repeat(indent)) + line));
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
147
170
|
if (isAgent)
|
|
148
171
|
lines.push(
|
|
149
172
|
theme.fg(
|
|
@@ -321,9 +344,11 @@ export function createLogViewer(options: LogViewerOptions) {
|
|
|
321
344
|
invalidate() {
|
|
322
345
|
body.invalidate();
|
|
323
346
|
},
|
|
324
|
-
update(nextHeader: string, logText: string, agent: boolean, context = "") {
|
|
347
|
+
update(nextHeader: string, logText: string, agent: boolean, context = "", timestamps?: (number | undefined)[]) {
|
|
325
348
|
header = nextHeader;
|
|
326
|
-
if (raw !== logText || isAgent !== agent || description !== context
|
|
349
|
+
if (raw !== logText || isAgent !== agent || description !== context ||
|
|
350
|
+
JSON.stringify(lineTimestamps) !== JSON.stringify(timestamps)) {
|
|
351
|
+
lineTimestamps = timestamps;
|
|
327
352
|
raw = logText;
|
|
328
353
|
isAgent = agent;
|
|
329
354
|
description = context;
|