@yusukeshib/pi-babysit 0.6.16 → 0.6.17

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 CHANGED
@@ -100,7 +100,10 @@ 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
101
  When babysit provides `output.timestamps.jsonl`, process lines begin with a muted
102
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
103
+ message. Agent lines show the capture time of their block's first available event
104
+ in the same left column, with the assistant/tool/result/error label beside the
105
+ first content line; streamed updates and duplicate results keep that time. This
106
+ is the time babysit received the first byte of the event or line, not an
104
107
  application event time. Narrow views place the date above the text. Old logs and
105
108
  lines without capture metadata remain readable without invented timestamps.
106
109
  Only the selected log is loaded, bounded to the latest 1 MiB / 2,000 display
package/index.ts CHANGED
@@ -3675,7 +3675,7 @@ export default function (pi: ExtensionAPI) {
3675
3675
  getTheme: () => ctx.ui.theme,
3676
3676
  readLog: (id) => {
3677
3677
  const isAgent = kindOf(id) === "subagent";
3678
- return { ...readViewerLog(logPath(id), !isAgent), isAgent };
3678
+ return { ...readViewerLog(logPath(id), true), isAgent };
3679
3679
  },
3680
3680
  });
3681
3681
  return sessionWidget;
package/log-viewer.ts CHANGED
@@ -31,7 +31,7 @@ function sanitize(text: string, sgr = false): string {
31
31
  (s) => (sgr && /^\x1b\[[\d;]*m$/.test(s) ? s : ""),
32
32
  );
33
33
  }
34
- type Block = { kind: "assistant" | "tool" | "result" | "error"; text: string };
34
+ type Block = { kind: "assistant" | "tool" | "result" | "error"; text: string; timestamp?: number };
35
35
  function contentText(content: unknown): string {
36
36
  if (typeof content === "string") return content;
37
37
  return Array.isArray(content)
@@ -41,15 +41,17 @@ function contentText(content: unknown): string {
41
41
  .join("")
42
42
  : "";
43
43
  }
44
- function agentBlocks(raw: string): Block[] {
44
+ function agentBlocks(raw: string, timestamps?: (number | undefined)[]): Block[] {
45
45
  const blocks: Block[] = [];
46
46
  let current: Block | undefined;
47
47
  const toolResults = new Map<string, Block>();
48
- const assistant = () =>
49
- (current ??=
50
- (blocks.push({ kind: "assistant", text: "" }),
51
- blocks[blocks.length - 1]));
52
- for (const line of raw.split("\n")) {
48
+ const assistant = (timestamp: number | undefined) => {
49
+ current ??= (blocks.push({ kind: "assistant", text: "", timestamp }), blocks[blocks.length - 1]);
50
+ current.timestamp ??= timestamp;
51
+ return current;
52
+ };
53
+ for (const [index, line] of raw.split(/\r\n|\r|\n/).entries()) {
54
+ const timestamp = timestamps?.[index];
53
55
  let e: any;
54
56
  try {
55
57
  e = JSON.parse(line);
@@ -62,28 +64,30 @@ function agentBlocks(raw: string): Block[] {
62
64
  if (["message_start", "message_update", "message_end"].includes(e.type)) {
63
65
  if (e.message?.role === "assistant") {
64
66
  const text = contentText(e.message.content);
65
- const b = assistant();
67
+ const b = assistant(timestamp);
66
68
  if (text) b.text = text; // cumulative/final snapshots replace streamed deltas
67
69
  if (e.message.errorMessage)
68
- blocks.push({ kind: "error", text: String(e.message.errorMessage) });
70
+ blocks.push({ kind: "error", text: String(e.message.errorMessage), timestamp });
69
71
  if (e.type === "message_end") current = undefined;
70
72
  } else if (
71
73
  !e.message &&
72
74
  e.assistantMessageEvent?.type === "text_delta" &&
73
75
  typeof e.assistantMessageEvent.delta === "string"
74
76
  ) {
75
- assistant().text += e.assistantMessageEvent.delta;
77
+ assistant(timestamp).text += e.assistantMessageEvent.delta;
76
78
  } else if (e.type === "message_end" && e.message?.role === "toolResult") {
77
79
  const id = e.message.toolCallId;
78
80
  const text = contentText(e.message.content);
79
81
  if (id && toolResults.has(id)) {
80
82
  const b = toolResults.get(id)!;
81
83
  b.text = text;
84
+ b.timestamp ??= timestamp;
82
85
  b.kind = e.message.isError ? "error" : b.kind;
83
86
  } else {
84
87
  const b: Block = {
85
88
  kind: e.message.isError ? "error" : "result",
86
89
  text,
90
+ timestamp,
87
91
  };
88
92
  blocks.push(b);
89
93
  if (id) toolResults.set(id, b);
@@ -93,10 +97,12 @@ function agentBlocks(raw: string): Block[] {
93
97
  blocks.push({
94
98
  kind: "tool",
95
99
  text: `${e.toolName ?? "tool"} ${JSON.stringify(e.args ?? {})}`,
100
+ timestamp,
96
101
  });
97
102
  } else if (e.type === "tool_execution_end") {
98
103
  const b: Block = {
99
104
  kind: e.isError ? "error" : "result",
105
+ timestamp,
100
106
  text:
101
107
  contentText(e.result?.content) ||
102
108
  String(e.error?.message ?? e.error ?? ""),
@@ -108,6 +114,7 @@ function agentBlocks(raw: string): Block[] {
108
114
  } else if (e.type === "error")
109
115
  blocks.push({
110
116
  kind: "error",
117
+ timestamp,
111
118
  text: String(
112
119
  e.message ?? e.error?.message ?? e.error ?? "Unknown error",
113
120
  ),
@@ -141,7 +148,7 @@ export function createLogViewer(options: LogViewerOptions) {
141
148
  const theme = options.getTheme();
142
149
  if (cache && cacheWidth === width && cacheTheme === theme) return cache;
143
150
  const blocks = isAgent
144
- ? agentBlocks(raw)
151
+ ? agentBlocks(raw, lineTimestamps)
145
152
  : [{ kind: "result" as const, text: sanitize(raw, true) }];
146
153
  const lines: string[] = description
147
154
  ? [
@@ -167,7 +174,25 @@ export function createLogViewer(options: LogViewerOptions) {
167
174
  }
168
175
  continue;
169
176
  }
170
- if (isAgent)
177
+ if (isAgent && b.timestamp !== undefined) {
178
+ const date = theme.fg("muted", dateLabel(b.timestamp));
179
+ const kind = theme.fg(b.kind === "error" ? "error" : b.kind === "assistant" ? "accent" : "toolTitle", `[${b.kind}]`);
180
+ const indent = visibleWidth(date) + 1;
181
+ const kindIndent = visibleWidth(kind) + 1;
182
+ if (width <= indent + kindIndent + 4) {
183
+ lines.push(...new Text(date, 0, 0).render(width));
184
+ lines.push(...new Text(`${kind} ${b.text}`, 0, 0).render(width));
185
+ } else {
186
+ const contentWidth = width - indent - kindIndent;
187
+ const rendered = b.kind === "assistant"
188
+ ? new Markdown(b.text, 0, 0, getMarkdownTheme()).render(contentWidth)
189
+ : new Text(b.text, 0, 0).render(contentWidth);
190
+ lines.push(...rendered.map((line, row) =>
191
+ `${date} ${row === 0 ? kind + " " : " ".repeat(kindIndent)}${line}`));
192
+ }
193
+ continue;
194
+ }
195
+ if (isAgent) {
171
196
  lines.push(
172
197
  theme.fg(
173
198
  b.kind === "error"
@@ -178,6 +203,7 @@ export function createLogViewer(options: LogViewerOptions) {
178
203
  `[${b.kind}]`,
179
204
  ),
180
205
  );
206
+ }
181
207
  lines.push(
182
208
  ...(b.kind === "assistant"
183
209
  ? new Markdown(b.text, 0, 0, getMarkdownTheme()).render(width)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yusukeshib/pi-babysit",
3
- "version": "0.6.16",
3
+ "version": "0.6.17",
4
4
  "description": "Run any shell command and pi subagents under babysit, with context-safe captured output.",
5
5
  "keywords": [
6
6
  "pi-package",