@yusukeshib/pi-babysit 0.3.13 → 0.3.15
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 +39 -18
- package/index.ts +986 -117
- package/package.json +1 -1
- package/rpc-stream-proxy.mjs +70 -10
- package/self-reap.ts +23 -10
package/package.json
CHANGED
package/rpc-stream-proxy.mjs
CHANGED
|
@@ -7,15 +7,45 @@
|
|
|
7
7
|
* are useful only while streaming; `message_end` is the authoritative final
|
|
8
8
|
* message. Persisting every snapshot makes babysit logs grow quadratically.
|
|
9
9
|
*
|
|
10
|
-
* This proxy forwards stdin byte-for-byte
|
|
11
|
-
*
|
|
12
|
-
*
|
|
10
|
+
* This proxy forwards stdin byte-for-byte. `message_update` snapshots are
|
|
11
|
+
* always compacted. Compact log mode is the default: redundant payloads on
|
|
12
|
+
* lifecycle/tool events are removed while authoritative `message_end`,
|
|
13
|
+
* response, and error events remain intact. Set PI_BABYSIT_RPC_LOG_MODE=standard
|
|
14
|
+
* to opt back into the legacy lifecycle payloads.
|
|
13
15
|
*/
|
|
14
16
|
import { spawn } from "node:child_process";
|
|
15
17
|
import { StringDecoder } from "node:string_decoder";
|
|
16
18
|
import { pathToFileURL } from "node:url";
|
|
17
19
|
|
|
18
|
-
|
|
20
|
+
function isParkedMessages(messages) {
|
|
21
|
+
if (!Array.isArray(messages)) return false;
|
|
22
|
+
for (let index = messages.length - 1; index >= 0; index--) {
|
|
23
|
+
const message = messages[index];
|
|
24
|
+
if (message?.role !== "toolResult") continue;
|
|
25
|
+
if (message.toolName === "process") return true;
|
|
26
|
+
if (message.toolName !== "babysit_run") continue;
|
|
27
|
+
if (message.details?.kind === "process" && message.details.status === "started") {
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
const text = Array.isArray(message.content)
|
|
31
|
+
? message.content
|
|
32
|
+
.filter((part) => part?.type === "text" && typeof part.text === "string")
|
|
33
|
+
.map((part) => part.text)
|
|
34
|
+
.join("")
|
|
35
|
+
: typeof message.content === "string"
|
|
36
|
+
? message.content
|
|
37
|
+
: "";
|
|
38
|
+
if (/^Process started \(id: [^)]+\)\. \[notify-on-exit\]\nLog: /.test(text)) {
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function compactRpcLine(
|
|
46
|
+
line,
|
|
47
|
+
mode = process.env.PI_BABYSIT_RPC_LOG_MODE ?? "compact",
|
|
48
|
+
) {
|
|
19
49
|
if (!line.trimStart().startsWith("{")) return line;
|
|
20
50
|
|
|
21
51
|
let event;
|
|
@@ -24,14 +54,44 @@ export function compactRpcLine(line) {
|
|
|
24
54
|
} catch {
|
|
25
55
|
return line;
|
|
26
56
|
}
|
|
27
|
-
if (event?.type !== "message_update") return line;
|
|
28
57
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
58
|
+
if (event?.type === "message_update") {
|
|
59
|
+
const { message: _cumulativeMessage, assistantMessageEvent, ...rest } = event;
|
|
60
|
+
if (!assistantMessageEvent || typeof assistantMessageEvent !== "object") {
|
|
61
|
+
return JSON.stringify(rest);
|
|
62
|
+
}
|
|
63
|
+
const { partial: _cumulativePartial, ...deltaEvent } = assistantMessageEvent;
|
|
64
|
+
return JSON.stringify({ ...rest, assistantMessageEvent: deltaEvent });
|
|
65
|
+
}
|
|
66
|
+
if (mode !== "compact") return line;
|
|
67
|
+
|
|
68
|
+
switch (event?.type) {
|
|
69
|
+
case "message_start": {
|
|
70
|
+
const { message, ...rest } = event;
|
|
71
|
+
return JSON.stringify({
|
|
72
|
+
...rest,
|
|
73
|
+
message:
|
|
74
|
+
message && typeof message === "object"
|
|
75
|
+
? { role: message.role, toolName: message.toolName, toolCallId: message.toolCallId }
|
|
76
|
+
: message,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
case "turn_end": {
|
|
80
|
+
const { message: _message, toolResults: _toolResults, ...rest } = event;
|
|
81
|
+
return JSON.stringify(rest);
|
|
82
|
+
}
|
|
83
|
+
case "agent_end": {
|
|
84
|
+
const { messages, ...rest } = event;
|
|
85
|
+
return JSON.stringify({ ...rest, piBabysitParked: isParkedMessages(messages) });
|
|
86
|
+
}
|
|
87
|
+
case "tool_execution_end": {
|
|
88
|
+
if (event.isError) return line;
|
|
89
|
+
const { result: _result, ...rest } = event;
|
|
90
|
+
return JSON.stringify(rest);
|
|
91
|
+
}
|
|
92
|
+
default:
|
|
93
|
+
return line;
|
|
32
94
|
}
|
|
33
|
-
const { partial: _cumulativePartial, ...deltaEvent } = assistantMessageEvent;
|
|
34
|
-
return JSON.stringify({ ...rest, assistantMessageEvent: deltaEvent });
|
|
35
95
|
}
|
|
36
96
|
|
|
37
97
|
function transformRpcOutput(input, output) {
|
package/self-reap.ts
CHANGED
|
@@ -23,10 +23,8 @@
|
|
|
23
23
|
*/
|
|
24
24
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
25
25
|
|
|
26
|
-
//
|
|
27
|
-
//
|
|
28
|
-
const NOTIFY_MARKER = "[notify-on-exit]";
|
|
29
|
-
|
|
26
|
+
// Loaded standalone into the subagent process via --extension, so the parked
|
|
27
|
+
// result-shape check below intentionally mirrors index.ts without importing it.
|
|
30
28
|
function parseDurMs(s: string | undefined): number | null {
|
|
31
29
|
if (!s || s === "none" || s === "off" || s === "0") return null;
|
|
32
30
|
const m = /^(\d+)(ms|s|m|h)?$/.exec(s.trim());
|
|
@@ -38,8 +36,15 @@ function parseDurMs(s: string | undefined): number | null {
|
|
|
38
36
|
|
|
39
37
|
// Same parked-turn rule as the parent: scan the run because models sometimes
|
|
40
38
|
// add an assistant note after the marker-bearing tool result.
|
|
41
|
-
function isParked(
|
|
42
|
-
messages:
|
|
39
|
+
export function isParked(
|
|
40
|
+
messages:
|
|
41
|
+
| {
|
|
42
|
+
role?: string;
|
|
43
|
+
toolName?: string;
|
|
44
|
+
content?: unknown;
|
|
45
|
+
details?: { kind?: string; status?: string };
|
|
46
|
+
}[]
|
|
47
|
+
| undefined,
|
|
43
48
|
): boolean {
|
|
44
49
|
if (!messages) return false;
|
|
45
50
|
for (let index = messages.length - 1; index >= 0; index--) {
|
|
@@ -47,12 +52,20 @@ function isParked(
|
|
|
47
52
|
if (message?.role !== "toolResult") continue;
|
|
48
53
|
if (message.toolName === "process") return true; // legacy pi-processes
|
|
49
54
|
if (message.toolName !== "babysit_run") continue;
|
|
50
|
-
|
|
51
|
-
const serialized = JSON.stringify(message.content ?? null);
|
|
52
|
-
if (serialized === "null" || serialized.includes(NOTIFY_MARKER)) return true;
|
|
53
|
-
} catch {
|
|
55
|
+
if (message.details?.kind === "process" && message.details.status === "started") {
|
|
54
56
|
return true;
|
|
55
57
|
}
|
|
58
|
+
const text = Array.isArray(message.content)
|
|
59
|
+
? message.content
|
|
60
|
+
.filter((part): part is { type: "text"; text: string } =>
|
|
61
|
+
Boolean(part && typeof part === "object" && part.type === "text" && typeof part.text === "string"),
|
|
62
|
+
)
|
|
63
|
+
.map((part) => part.text)
|
|
64
|
+
.join("")
|
|
65
|
+
: typeof message.content === "string"
|
|
66
|
+
? message.content
|
|
67
|
+
: "";
|
|
68
|
+
if (/^Process started \(id: [^)]+\)\. \[notify-on-exit\]\nLog: /.test(text)) return true;
|
|
56
69
|
}
|
|
57
70
|
return false;
|
|
58
71
|
}
|