pi-cohort 5.3.2 → 5.3.4
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/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [Unreleased]
|
|
4
|
+
|
|
5
|
+
## [5.3.4] - 2026-09-07
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- A fanout lead no longer loses its own `subagent` call/result before its next turn: the child-context filter now only strips `subagent` history inherited from a forked parent (captured at `session_start`), not the child's own delegation made during its own session. Previously a deterministic provider would see the delegation as never having happened and repeat it instead of finishing.
|
|
10
|
+
|
|
11
|
+
## [5.3.3] - 2026-09-07
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
|
|
15
|
+
- Nested foreground delegation no longer crashes when a grandchild reports cost progress because child-safe state now initializes the required grand-total accumulator. ([#13](https://github.com/jjuraszek/pi-cohort/issues/13))
|
|
16
|
+
|
|
3
17
|
## [5.3.2] - 2026-09-06
|
|
4
18
|
|
|
5
19
|
### Fixed
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-cohort",
|
|
3
|
-
"version": "5.3.
|
|
3
|
+
"version": "5.3.4",
|
|
4
4
|
"description": "Delegate Pi work to focused child agents: code review, scouting, implementation, parallel audits, saved chains, and background jobs.",
|
|
5
5
|
"author": "Jacek Juraszek",
|
|
6
6
|
"license": "MIT",
|
|
@@ -13,6 +13,7 @@ import { SubagentParams } from "./schemas.ts";
|
|
|
13
13
|
import { loadConfig } from "./config.ts";
|
|
14
14
|
import { deriveForwardedFlags } from "../runs/shared/forward-flags.ts";
|
|
15
15
|
import type { Details, SubagentState } from "../shared/types.ts";
|
|
16
|
+
import { emptyGrandTotal } from "./grand-total.ts";
|
|
16
17
|
|
|
17
18
|
function getSubagentSessionRoot(parentSessionFile: string | null): string {
|
|
18
19
|
if (parentSessionFile) {
|
|
@@ -32,6 +33,7 @@ function createChildSafeState(): SubagentState {
|
|
|
32
33
|
baseCwd: "",
|
|
33
34
|
currentSessionId: null,
|
|
34
35
|
asyncJobs: new Map(),
|
|
36
|
+
grandTotal: emptyGrandTotal(),
|
|
35
37
|
foregroundRuns: new Map(),
|
|
36
38
|
foregroundControls: new Map(),
|
|
37
39
|
lastForegroundControlId: null,
|
|
@@ -115,34 +115,44 @@ function isParentOnlySubagentMessage(message: unknown): boolean {
|
|
|
115
115
|
&& PARENT_ONLY_CUSTOM_MESSAGE_TYPES.has(m.customType);
|
|
116
116
|
}
|
|
117
117
|
|
|
118
|
-
function isSubagentToolResultMessage(message: unknown): boolean {
|
|
119
|
-
const m = message as { role?: string; toolName?: string };
|
|
120
|
-
|
|
118
|
+
function isSubagentToolResultMessage(message: unknown, inheritedToolCallIds: ReadonlySet<string>): boolean {
|
|
119
|
+
const m = message as { role?: string; toolName?: string; toolCallId?: string };
|
|
120
|
+
if (m?.role !== "toolResult" || m.toolName !== "subagent") return false;
|
|
121
|
+
return typeof m.toolCallId === "string" && inheritedToolCallIds.has(m.toolCallId);
|
|
121
122
|
}
|
|
122
123
|
|
|
123
|
-
function
|
|
124
|
-
const b = block as { type?: string; name?: string };
|
|
125
|
-
|
|
124
|
+
function isInheritedSubagentToolCallBlock(block: unknown, inheritedToolCallIds: ReadonlySet<string>): boolean {
|
|
125
|
+
const b = block as { type?: string; name?: string; id?: string };
|
|
126
|
+
if (b?.type !== "toolCall" || b.name !== "subagent") return false;
|
|
127
|
+
return typeof b.id === "string" && inheritedToolCallIds.has(b.id);
|
|
126
128
|
}
|
|
127
129
|
|
|
128
|
-
function stripAssistantSubagentToolCallBlocks(message: unknown): unknown | undefined {
|
|
130
|
+
function stripAssistantSubagentToolCallBlocks(message: unknown, inheritedToolCallIds: ReadonlySet<string>): unknown | undefined {
|
|
129
131
|
const m = message as { role?: string; content?: unknown };
|
|
130
132
|
if (m?.role !== "assistant" || !Array.isArray(m.content)) return message;
|
|
131
|
-
const filteredContent = m.content.filter((block) => !
|
|
133
|
+
const filteredContent = m.content.filter((block) => !isInheritedSubagentToolCallBlock(block, inheritedToolCallIds));
|
|
132
134
|
if (filteredContent.length === m.content.length) return message;
|
|
133
135
|
if (filteredContent.length === 0) return undefined;
|
|
134
136
|
return { ...m, content: filteredContent };
|
|
135
137
|
}
|
|
136
138
|
|
|
137
|
-
|
|
139
|
+
/**
|
|
140
|
+
* Strips parent-only orchestration artifacts from a child's context.
|
|
141
|
+
*
|
|
142
|
+
* `inheritedToolCallIds` distinguishes history inherited from a forked parent
|
|
143
|
+
* session (ids captured before the child's own first turn) from the child's
|
|
144
|
+
* own `subagent` calls/results made during its own session - only the former
|
|
145
|
+
* is stripped.
|
|
146
|
+
*/
|
|
147
|
+
export function stripParentOnlySubagentMessages(messages: unknown[], inheritedToolCallIds: ReadonlySet<string>): unknown[] {
|
|
138
148
|
let changed = false;
|
|
139
149
|
const filtered: unknown[] = [];
|
|
140
150
|
for (const message of messages) {
|
|
141
|
-
if (isParentOnlySubagentMessage(message) || isSubagentToolResultMessage(message)) {
|
|
151
|
+
if (isParentOnlySubagentMessage(message) || isSubagentToolResultMessage(message, inheritedToolCallIds)) {
|
|
142
152
|
changed = true;
|
|
143
153
|
continue;
|
|
144
154
|
}
|
|
145
|
-
const stripped = stripAssistantSubagentToolCallBlocks(message);
|
|
155
|
+
const stripped = stripAssistantSubagentToolCallBlocks(message, inheritedToolCallIds);
|
|
146
156
|
if (stripped === undefined) {
|
|
147
157
|
changed = true;
|
|
148
158
|
continue;
|
|
@@ -153,6 +163,19 @@ export function stripParentOnlySubagentMessages(messages: unknown[]): unknown[]
|
|
|
153
163
|
return changed ? filtered : messages;
|
|
154
164
|
}
|
|
155
165
|
|
|
166
|
+
function collectSubagentToolCallIds(entries: readonly unknown[]): Set<string> {
|
|
167
|
+
const ids = new Set<string>();
|
|
168
|
+
for (const entry of entries) {
|
|
169
|
+
const e = entry as { type?: string; message?: { role?: string; content?: unknown } };
|
|
170
|
+
if (e?.type !== "message" || e.message?.role !== "assistant" || !Array.isArray(e.message.content)) continue;
|
|
171
|
+
for (const block of e.message.content) {
|
|
172
|
+
const b = block as { type?: string; name?: string; id?: string };
|
|
173
|
+
if (b?.type === "toolCall" && b.name === "subagent" && typeof b.id === "string") ids.add(b.id);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return ids;
|
|
177
|
+
}
|
|
178
|
+
|
|
156
179
|
export default function registerSubagentPromptRuntime(pi: ExtensionAPI): void {
|
|
157
180
|
const structuredOutputPath = process.env[STRUCTURED_OUTPUT_CAPTURE_ENV];
|
|
158
181
|
const structuredSchemaPath = process.env[STRUCTURED_OUTPUT_SCHEMA_ENV];
|
|
@@ -192,11 +215,21 @@ export default function registerSubagentPromptRuntime(pi: ExtensionAPI): void {
|
|
|
192
215
|
});
|
|
193
216
|
}
|
|
194
217
|
|
|
195
|
-
|
|
196
|
-
onRuntimeEvent
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
218
|
+
let inheritedSubagentToolCallIds: ReadonlySet<string> = new Set<string>();
|
|
219
|
+
const onRuntimeEvent = pi.on as unknown as (
|
|
220
|
+
event: string,
|
|
221
|
+
handler: (event: unknown, ctx?: { sessionManager?: { getBranch(): unknown[] } }) => unknown,
|
|
222
|
+
) => void;
|
|
223
|
+
onRuntimeEvent("session_start", (_event: unknown, ctx?: { sessionManager?: { getBranch(): unknown[] } }) => {
|
|
224
|
+
// Establishes the inherited/own boundary once, before the child's own first turn: any
|
|
225
|
+
// `subagent` call already in the branch at this point came from the (forked) parent.
|
|
226
|
+
inheritedSubagentToolCallIds = collectSubagentToolCallIds(ctx?.sessionManager?.getBranch() ?? []);
|
|
227
|
+
});
|
|
228
|
+
onRuntimeEvent("context", (event: unknown) => {
|
|
229
|
+
const { messages } = event as { messages: unknown[] };
|
|
230
|
+
const stripped = stripParentOnlySubagentMessages(messages, inheritedSubagentToolCallIds);
|
|
231
|
+
if (stripped === messages) return undefined;
|
|
232
|
+
return { messages: stripped };
|
|
200
233
|
});
|
|
201
234
|
|
|
202
235
|
onRuntimeEvent("before_agent_start", async (event: { systemPrompt: string }) => {
|