@mystilleef/pi-subagent 0.6.0 → 0.7.0

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
@@ -151,7 +151,7 @@ executable automation.
151
151
  **Environment variables:**
152
152
 
153
153
  - `PI_SUBAGENT_DEPTH`: nested subagent depth guard. Nested calls stop at
154
- depth `1`.
154
+ depth `2`.
155
155
  - `PI_SUBAGENT_MAX_OUTPUT_BYTES`: max returned output bytes. Default:
156
156
  `50000`.
157
157
  - `PI_SUBAGENT_MAX_OUTPUT_LINES`: max returned output lines. Default:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mystilleef/pi-subagent",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "description": "Pi subagent for the SPAE Framework",
5
5
  "author": "Lateef Alabi-Oki <mystilleef@gmail.com>",
6
6
  "license": "MIT",
@@ -1,32 +1,249 @@
1
+ import { createHash } from "node:crypto";
2
+ import * as fsPromises from "node:fs/promises";
1
3
  import path from "node:path";
2
4
  import {
3
5
  type AgentDiscoveryResult,
6
+ type AgentDiscoveryScopeResult,
4
7
  type AgentScope,
8
+ type AgentSource,
5
9
  discoverAgentsAsync,
10
+ emptyScopeResult,
11
+ getUserAgentsDir,
12
+ readMarkdownDirEntriesAsync,
6
13
  } from "./agents.js";
7
14
 
8
- export type AgentDiscoveryCacheEntry = AgentDiscoveryResult & { ts: number };
15
+ interface AgentDiscoveryScopeSnapshot {
16
+ markdownFiles: string[];
17
+ fileHashes: Record<string, string | null>;
18
+ }
19
+
20
+ type AgentDiscoverySnapshots = Record<AgentSource, AgentDiscoveryScopeSnapshot>;
21
+
22
+ export type AgentDiscoveryCacheEntry = AgentDiscoveryResult & {
23
+ ts: number;
24
+ snapshots?: AgentDiscoverySnapshots;
25
+ };
9
26
  export type AgentDiscoveryCache = Map<string, AgentDiscoveryCacheEntry>;
10
27
  export const AGENT_DISCOVERY_CACHE_TTL_MS = 300_000;
11
28
  const sharedAgentDiscoveryCache: AgentDiscoveryCache = new Map();
29
+ const AGENT_SOURCES = ["user", "project"] as const;
30
+
31
+ interface CacheOperationContext {
32
+ cwd: string;
33
+ cache: AgentDiscoveryCache;
34
+ ts: number;
35
+ cacheTtlMs: number;
36
+ }
12
37
 
13
38
  export function resetAgentDiscoveryCache(): void {
14
39
  sharedAgentDiscoveryCache.clear();
15
40
  }
16
41
 
42
+ function cacheKey(cwd: string, scope: AgentScope): string {
43
+ return `${path.resolve(cwd)}\0${scope}`;
44
+ }
45
+
46
+ function getFreshCacheEntry(
47
+ ctx: CacheOperationContext,
48
+ key: string,
49
+ ): AgentDiscoveryCacheEntry | undefined {
50
+ const entry = ctx.cache.get(key);
51
+ return entry && ctx.ts - entry.ts <= ctx.cacheTtlMs ? entry : undefined;
52
+ }
53
+
54
+ function emptySnapshot(): AgentDiscoveryScopeSnapshot {
55
+ return { markdownFiles: [], fileHashes: {} };
56
+ }
57
+
58
+ function cloneScopeSnapshot(
59
+ snapshot: AgentDiscoveryScopeSnapshot | undefined,
60
+ ): AgentDiscoveryScopeSnapshot {
61
+ if (!snapshot) return emptySnapshot();
62
+ return {
63
+ markdownFiles: [...snapshot.markdownFiles],
64
+ fileHashes: { ...snapshot.fileHashes },
65
+ };
66
+ }
67
+
68
+ function equalStringSets(a: string[], b: string[]): boolean {
69
+ if (a.length !== b.length) return false;
70
+ const set = new Set(a);
71
+ return b.every((value) => set.has(value));
72
+ }
73
+
74
+ function snapshotsEqual(
75
+ left: AgentDiscoveryScopeSnapshot,
76
+ right: AgentDiscoveryScopeSnapshot,
77
+ ): boolean {
78
+ if (!equalStringSets(left.markdownFiles, right.markdownFiles)) return false;
79
+ return left.markdownFiles.every(
80
+ (fileName) => left.fileHashes[fileName] === right.fileHashes[fileName],
81
+ );
82
+ }
83
+
84
+ function scopeAgentsMatchListing(
85
+ scopeResult: AgentDiscoveryScopeResult,
86
+ source: AgentSource,
87
+ dir: string | null,
88
+ ): boolean {
89
+ if (!dir)
90
+ return (
91
+ scopeResult.agents.length === 0 && scopeResult.markdownFiles.length === 0
92
+ );
93
+ const listedFiles = new Set(scopeResult.markdownFiles);
94
+ const resolvedDir = path.resolve(dir);
95
+ return scopeResult.agents.every((agent) => {
96
+ const fileName = path.basename(agent.filePath);
97
+ return (
98
+ agent.source === source &&
99
+ listedFiles.has(fileName) &&
100
+ path.resolve(path.dirname(agent.filePath)) === resolvedDir
101
+ );
102
+ });
103
+ }
104
+
105
+ async function hashMarkdownFileAsync(
106
+ dir: string,
107
+ fileName: string,
108
+ ): Promise<string | null> {
109
+ try {
110
+ return createHash("sha256")
111
+ .update(await fsPromises.readFile(path.join(dir, fileName)))
112
+ .digest("hex");
113
+ } catch {
114
+ return null;
115
+ }
116
+ }
117
+
118
+ async function buildScopeSnapshotAsync(
119
+ dir: string | null,
120
+ ): Promise<AgentDiscoveryScopeSnapshot> {
121
+ if (!dir) return emptySnapshot();
122
+ const entries = await readMarkdownDirEntriesAsync(dir);
123
+ const markdownFiles = entries.map((entry) => entry.name);
124
+ const hashPairs = await Promise.all(
125
+ markdownFiles.map(
126
+ async (fileName) =>
127
+ [fileName, await hashMarkdownFileAsync(dir, fileName)] as const,
128
+ ),
129
+ );
130
+ return { markdownFiles, fileHashes: Object.fromEntries(hashPairs) };
131
+ }
132
+
133
+ async function buildCacheSnapshotsAsync(
134
+ discovery: AgentDiscoveryResult,
135
+ ): Promise<AgentDiscoverySnapshots> {
136
+ const [user, project] = await Promise.all([
137
+ buildScopeSnapshotAsync(getUserAgentsDir()),
138
+ buildScopeSnapshotAsync(discovery.projectAgentsDir),
139
+ ]);
140
+ return { user, project };
141
+ }
142
+
143
+ async function canTrustDerivedScopeAsync(
144
+ source: AgentSource,
145
+ bothEntry: AgentDiscoveryCacheEntry,
146
+ ): Promise<boolean> {
147
+ const dir =
148
+ source === "user" ? getUserAgentsDir() : bothEntry.projectAgentsDir;
149
+ const scopeResult = bothEntry.scopes[source];
150
+ const cachedSnapshot = bothEntry.snapshots?.[source];
151
+ if (!cachedSnapshot) return false;
152
+ if (!scopeAgentsMatchListing(scopeResult, source, dir)) return false;
153
+ if (!equalStringSets(scopeResult.markdownFiles, cachedSnapshot.markdownFiles))
154
+ return false;
155
+ return snapshotsEqual(cachedSnapshot, await buildScopeSnapshotAsync(dir));
156
+ }
157
+
158
+ function buildSourceRecord<T>(
159
+ source: AgentSource,
160
+ value: T,
161
+ empty: () => T,
162
+ ): Record<AgentSource, T> {
163
+ const record: Record<AgentSource, T> = { user: empty(), project: empty() };
164
+ record[source] = value;
165
+ return record;
166
+ }
167
+ function createDerivedCacheEntry(
168
+ bothEntry: AgentDiscoveryCacheEntry,
169
+ source: AgentSource,
170
+ ): AgentDiscoveryCacheEntry {
171
+ const { agents, markdownFiles } = bothEntry.scopes[source];
172
+ const clonedScope: AgentDiscoveryScopeResult = {
173
+ agents: [...agents],
174
+ markdownFiles: [...markdownFiles],
175
+ };
176
+ return {
177
+ agents: clonedScope.agents,
178
+ projectAgentsDir: bothEntry.projectAgentsDir,
179
+ scopes: buildSourceRecord(source, clonedScope, emptyScopeResult),
180
+ ts: bothEntry.ts,
181
+ snapshots: buildSourceRecord(
182
+ source,
183
+ cloneScopeSnapshot(bothEntry.snapshots?.[source]),
184
+ emptySnapshot,
185
+ ),
186
+ };
187
+ }
188
+
189
+ async function discoverAndCacheAsync(
190
+ ctx: CacheOperationContext,
191
+ scope: AgentScope,
192
+ ): Promise<AgentDiscoveryCacheEntry> {
193
+ const discovery = await discoverAgentsAsync(ctx.cwd, scope);
194
+ const entry: AgentDiscoveryCacheEntry = {
195
+ ...discovery,
196
+ ts: ctx.ts,
197
+ snapshots: await buildCacheSnapshotsAsync(discovery),
198
+ };
199
+ ctx.cache.set(cacheKey(ctx.cwd, scope), entry);
200
+ if (scope === "both") {
201
+ await primeScopedCacheEntriesAsync(ctx, entry);
202
+ }
203
+ return entry;
204
+ }
205
+
206
+ async function deriveOrDiscoverScopedEntryAsync(
207
+ ctx: CacheOperationContext,
208
+ source: AgentSource,
209
+ bothEntry: AgentDiscoveryCacheEntry,
210
+ ): Promise<AgentDiscoveryCacheEntry> {
211
+ if (await canTrustDerivedScopeAsync(source, bothEntry)) {
212
+ const entry = createDerivedCacheEntry(bothEntry, source);
213
+ ctx.cache.set(cacheKey(ctx.cwd, source), entry);
214
+ return entry;
215
+ }
216
+ return discoverAndCacheAsync(ctx, source);
217
+ }
218
+
219
+ async function primeScopedCacheEntriesAsync(
220
+ ctx: CacheOperationContext,
221
+ bothEntry: AgentDiscoveryCacheEntry,
222
+ ): Promise<void> {
223
+ for (const source of AGENT_SOURCES) {
224
+ const scopedKey = cacheKey(ctx.cwd, source);
225
+ if (getFreshCacheEntry(ctx, scopedKey)) continue;
226
+ await deriveOrDiscoverScopedEntryAsync(ctx, source, bothEntry);
227
+ }
228
+ }
229
+
17
230
  export async function getCachedAgentDiscovery(
18
231
  cwd: string,
19
232
  scope: AgentScope,
20
233
  cache: AgentDiscoveryCache = sharedAgentDiscoveryCache,
21
234
  cacheTtlMs = AGENT_DISCOVERY_CACHE_TTL_MS,
22
235
  ): Promise<AgentDiscoveryCacheEntry> {
23
- const key = `${path.resolve(cwd)}\0${scope}`;
24
- const now = Date.now();
25
- const entry = cache.get(key);
26
- if (entry && now - entry.ts <= cacheTtlMs) return entry;
27
- const nextEntry = { ...(await discoverAgentsAsync(cwd, scope)), ts: now };
28
- cache.set(key, nextEntry);
29
- return nextEntry;
236
+ const ctx: CacheOperationContext = { cwd, cache, ts: Date.now(), cacheTtlMs };
237
+ const key = cacheKey(cwd, scope);
238
+ const entry = getFreshCacheEntry(ctx, key);
239
+ if (entry) return entry;
240
+ if (scope !== "both") {
241
+ const bothEntry = getFreshCacheEntry(ctx, cacheKey(cwd, "both"));
242
+ if (bothEntry) {
243
+ return deriveOrDiscoverScopedEntryAsync(ctx, scope, bothEntry);
244
+ }
245
+ }
246
+ return discoverAndCacheAsync(ctx, scope);
30
247
  }
31
248
 
32
249
  export async function getCachedAgentCompletions(
@@ -1,21 +1,10 @@
1
- /**
2
- * Agent discovery and configuration
3
- */
4
-
5
1
  import type { Dirent } from "node:fs";
6
2
  import * as fsPromises from "node:fs/promises";
7
3
  import * as path from "node:path";
8
4
  import { getAgentDir, parseFrontmatter } from "@earendil-works/pi-coding-agent";
9
5
 
10
- export type AgentScope = "user" | "project" | "both";
11
-
12
- export type ThinkingLevel =
13
- | "off"
14
- | "minimal"
15
- | "low"
16
- | "medium"
17
- | "high"
18
- | "xhigh";
6
+ export type AgentSource = "user" | "project";
7
+ export type AgentScope = AgentSource | "both";
19
8
 
20
9
  const THINKING_LEVELS = [
21
10
  "off",
@@ -26,6 +15,8 @@ const THINKING_LEVELS = [
26
15
  "xhigh",
27
16
  ] as const;
28
17
 
18
+ export type ThinkingLevel = (typeof THINKING_LEVELS)[number];
19
+
29
20
  export interface AgentConfig {
30
21
  name: string;
31
22
  description: string;
@@ -33,24 +24,37 @@ export interface AgentConfig {
33
24
  skills?: string[];
34
25
  thinking?: ThinkingLevel;
35
26
  systemPrompt: string;
36
- source: "user" | "project";
27
+ source: AgentSource;
37
28
  filePath: string;
38
29
  }
39
30
 
31
+ export interface AgentDiscoveryScopeResult {
32
+ agents: AgentConfig[];
33
+ markdownFiles: string[];
34
+ }
35
+
40
36
  export interface AgentDiscoveryResult {
41
37
  agents: AgentConfig[];
42
38
  projectAgentsDir: string | null;
39
+ scopes: Record<AgentSource, AgentDiscoveryScopeResult>;
43
40
  }
44
41
 
45
42
  function mergeAgentLists(
46
- userAgents: AgentConfig[],
47
- projectAgents: AgentConfig[],
43
+ userResult: AgentDiscoveryScopeResult,
44
+ projectResult: AgentDiscoveryScopeResult,
48
45
  projectAgentsDir: string | null,
49
46
  ): AgentDiscoveryResult {
50
47
  const agentMap = new Map<string, AgentConfig>();
51
- for (const agent of userAgents) agentMap.set(agent.name, agent);
52
- for (const agent of projectAgents) agentMap.set(agent.name, agent);
53
- return { agents: Array.from(agentMap.values()), projectAgentsDir };
48
+ for (const agent of userResult.agents) agentMap.set(agent.name, agent);
49
+ for (const agent of projectResult.agents) agentMap.set(agent.name, agent);
50
+ return {
51
+ agents: Array.from(agentMap.values()),
52
+ projectAgentsDir,
53
+ scopes: {
54
+ user: userResult,
55
+ project: projectResult,
56
+ },
57
+ };
54
58
  }
55
59
 
56
60
  function parseCommaList(raw: unknown): string[] | undefined {
@@ -72,7 +76,7 @@ function parseThinkingLevel(raw: unknown): ThinkingLevel | undefined {
72
76
 
73
77
  function parseAgentConfig(
74
78
  content: string,
75
- source: "user" | "project",
79
+ source: AgentSource,
76
80
  filePath: string,
77
81
  ): AgentConfig | null {
78
82
  let parsed: ReturnType<typeof parseFrontmatter<Record<string, unknown>>>;
@@ -116,35 +120,65 @@ function parseAgentConfig(
116
120
  };
117
121
  }
118
122
 
119
- async function loadAgentsFromDirAsync(
123
+ async function loadAgentEntryAsync(
120
124
  dir: string,
121
- source: "user" | "project",
122
- ): Promise<AgentConfig[]> {
123
- const agents: AgentConfig[] = [];
124
- if (!(await isDirectoryAsync(dir))) return agents;
125
- let entries: Dirent[];
125
+ entryName: string,
126
+ source: AgentSource,
127
+ ): Promise<AgentConfig | null> {
128
+ const filePath = path.join(dir, entryName);
129
+ let content: string;
126
130
  try {
127
- entries = await fsPromises.readdir(dir, { withFileTypes: true });
131
+ content = await fsPromises.readFile(filePath, "utf-8");
128
132
  } catch {
129
- return agents;
133
+ return null;
130
134
  }
131
- for (const entry of entries) {
132
- if (!entry.name.endsWith(".md")) continue;
133
- if (!entry.isFile() && !entry.isSymbolicLink()) continue;
134
- const filePath = path.join(dir, entry.name);
135
- let content: string;
136
- try {
137
- content = await fsPromises.readFile(filePath, "utf-8");
138
- } catch {
139
- continue;
140
- }
141
- const agent = parseAgentConfig(content, source, filePath);
142
- if (agent) agents.push(agent);
135
+ return parseAgentConfig(content, source, filePath);
136
+ }
137
+
138
+ export function emptyScopeResult(): AgentDiscoveryScopeResult {
139
+ return { agents: [], markdownFiles: [] };
140
+ }
141
+
142
+ async function loadAgentsFromDirAsync(
143
+ dir: string,
144
+ source: AgentSource,
145
+ ): Promise<AgentDiscoveryScopeResult> {
146
+ const markdownEntries = await readMarkdownDirEntriesAsync(dir);
147
+ const markdownFiles = markdownEntries.map((entry) => entry.name);
148
+ const parsedAgents = await Promise.all(
149
+ markdownEntries.map((entry) =>
150
+ loadAgentEntryAsync(dir, entry.name, source),
151
+ ),
152
+ );
153
+ const agents = parsedAgents.filter(
154
+ (agent): agent is AgentConfig => agent !== null,
155
+ );
156
+ return { agents, markdownFiles };
157
+ }
158
+
159
+ export function isMarkdownDirent(entry: Dirent): boolean {
160
+ return (
161
+ entry.name.endsWith(".md") && (entry.isFile() || entry.isSymbolicLink())
162
+ );
163
+ }
164
+
165
+ export async function readMarkdownDirEntriesAsync(
166
+ dir: string | null,
167
+ ): Promise<Dirent[]> {
168
+ if (!dir) return [];
169
+ try {
170
+ const entries = await fsPromises.readdir(dir, { withFileTypes: true });
171
+ return entries.filter(isMarkdownDirent);
172
+ } catch {
173
+ return [];
143
174
  }
144
- return agents;
145
175
  }
146
176
 
147
- async function isDirectoryAsync(p: string): Promise<boolean> {
177
+ export function getUserAgentsDir(): string {
178
+ return path.join(getAgentDir(), "agents");
179
+ }
180
+
181
+ export async function isDirectoryAsync(p: string): Promise<boolean> {
148
182
  try {
149
183
  return (await fsPromises.stat(p)).isDirectory();
150
184
  } catch {
@@ -169,15 +203,17 @@ export async function discoverAgentsAsync(
169
203
  cwd: string,
170
204
  scope: AgentScope,
171
205
  ): Promise<AgentDiscoveryResult> {
172
- const userDir = path.join(getAgentDir(), "agents");
206
+ const userDir = getUserAgentsDir();
173
207
  const projectAgentsDir = await findNearestProjectAgentsDirAsync(cwd);
174
- const [userAgents, projectAgents] = await Promise.all([
175
- scope === "project" ? [] : loadAgentsFromDirAsync(userDir, "user"),
208
+ const [userDiscovery, projectDiscovery] = await Promise.all([
209
+ scope === "project"
210
+ ? emptyScopeResult()
211
+ : loadAgentsFromDirAsync(userDir, "user"),
176
212
  scope === "user" || !projectAgentsDir
177
- ? []
213
+ ? emptyScopeResult()
178
214
  : loadAgentsFromDirAsync(projectAgentsDir, "project"),
179
215
  ]);
180
- return mergeAgentLists(userAgents, projectAgents, projectAgentsDir);
216
+ return mergeAgentLists(userDiscovery, projectDiscovery, projectAgentsDir);
181
217
  }
182
218
 
183
219
  export function formatAgentList(
@@ -1,14 +1,87 @@
1
- type ChildKnownEvent =
1
+ import { makeToolPreview } from "../output/normalize.js";
2
+ import type { ToolActivity } from "../shared/types.js";
3
+
4
+ // Extracts results[0] from details; null-safe for malformed input.
5
+ function tryFirstResult(details: unknown): Record<string, unknown> | null {
6
+ try {
7
+ if (typeof details !== "object" || details === null) return null;
8
+ const results = (details as Record<string, unknown>).results;
9
+ if (!Array.isArray(results) || results.length === 0) return null;
10
+ const nested = results[0];
11
+ if (typeof nested !== "object" || nested === null) return null;
12
+ return nested as Record<string, unknown>;
13
+ } catch {
14
+ return null;
15
+ }
16
+ }
17
+
18
+ // Malformed details (null result) falls back to { toolName }.
19
+ function parseToolActivity(
20
+ toolName: string,
21
+ partialResult: { content?: unknown; details?: unknown },
22
+ ): ToolActivity {
23
+ const nestedRecord = tryFirstResult(partialResult.details);
24
+ if (!nestedRecord) return { toolName, inputSummary: toolName };
25
+ const isSubagent = toolName === "subagent";
26
+ const activity: ToolActivity = { toolName };
27
+ const agent = typeof nestedRecord.agent === "string" && nestedRecord.agent;
28
+ activity.inputSummary =
29
+ isSubagent && agent ? makeToolPreview(toolName, nestedRecord) : toolName;
30
+ if (
31
+ typeof nestedRecord.instanceName === "string" &&
32
+ nestedRecord.instanceName
33
+ ) {
34
+ activity.instanceName = nestedRecord.instanceName;
35
+ }
36
+ const progress = nestedRecord.progress;
37
+ if (typeof progress === "object" && progress !== null) {
38
+ const activeToolActivity = (progress as Record<string, unknown>)
39
+ .activeToolActivity;
40
+ if (
41
+ typeof activeToolActivity === "object" &&
42
+ activeToolActivity !== null &&
43
+ typeof (activeToolActivity as Record<string, unknown>).toolName ===
44
+ "string"
45
+ ) {
46
+ const childActivity = activeToolActivity as ToolActivity;
47
+ activity.child = childActivity;
48
+ // Subagent delegates inputSummary to its own agent name, not child
49
+ if (
50
+ !isSubagent &&
51
+ typeof childActivity.inputSummary === "string" &&
52
+ childActivity.inputSummary
53
+ ) {
54
+ activity.inputSummary = childActivity.inputSummary;
55
+ }
56
+ }
57
+ }
58
+ return activity;
59
+ }
60
+
61
+ export type ChildKnownEvent =
2
62
  | { type: "message_end"; message: unknown }
3
63
  | { type: "tool_result_end"; message: unknown }
4
- | { type: "agent_end"; messages?: unknown; stopReason?: string };
64
+ | { type: "agent_end"; messages?: unknown; stopReason?: string }
65
+ | {
66
+ type: "tool_execution_update";
67
+ toolName: string;
68
+ partialResult: { content?: unknown; details?: unknown };
69
+ toolActivity: ToolActivity;
70
+ };
5
71
 
6
72
  export type ChildEventParseResult =
7
73
  | { kind: "known"; event: ChildKnownEvent }
8
74
  | { kind: "unknown"; event: unknown }
9
75
  | { kind: "invalid"; line: string };
10
76
 
11
- const KNOWN_TYPES = new Set(["message_end", "tool_result_end", "agent_end"]);
77
+ export const TOOL_EXECUTION_UPDATE_EVENT = "tool_execution_update" as const;
78
+
79
+ const KNOWN_TYPES = new Set([
80
+ "message_end",
81
+ "tool_result_end",
82
+ "agent_end",
83
+ TOOL_EXECUTION_UPDATE_EVENT,
84
+ ]);
12
85
 
13
86
  export function parseChildEventLine(line: string): ChildEventParseResult {
14
87
  if (typeof line !== "string" || !line.trim())
@@ -26,6 +99,20 @@ export function parseChildEventLine(line: string): ChildEventParseResult {
26
99
  typeof (event as Record<string, unknown>).type === "string" &&
27
100
  KNOWN_TYPES.has((event as Record<string, unknown>).type as string)
28
101
  ) {
102
+ const record = event as Record<string, unknown>;
103
+ if (record.type === TOOL_EXECUTION_UPDATE_EVENT) {
104
+ if (
105
+ typeof record.toolName !== "string" ||
106
+ typeof record.partialResult !== "object" ||
107
+ record.partialResult === null
108
+ ) {
109
+ return { kind: "unknown", event };
110
+ }
111
+ record.toolActivity = parseToolActivity(
112
+ record.toolName as string,
113
+ record.partialResult as { content?: unknown; details?: unknown },
114
+ );
115
+ }
29
116
  return { kind: "known", event: event as ChildKnownEvent };
30
117
  }
31
118
  return { kind: "unknown", event };