@youngjurry/pi-agents 0.7.1

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 ADDED
@@ -0,0 +1,116 @@
1
+ # Changelog
2
+
3
+ ## 0.7.1 - 2026-09-01
4
+
5
+ - Order the `/agents` picker by latest task assignment, newest first, instead of alphabetically.
6
+ - Persist `lastAssignedAt` separately from noisy session activity timestamps.
7
+ - Move reused agents to the top after a successful `followup_task` without reshuffling on streaming, tools, completion, loading, or read-only inspection.
8
+ - Preserve canonical path ordering in `list_agents(view="status")` and FIFO ordering in the execution queue.
9
+ - Add public npm package metadata, reproducible development dependencies, and prepublish checks for `@youngjurry/pi-agents`.
10
+
11
+ ## 0.7.0 - 2026-09-01
12
+
13
+ - Add validated global `defaultThinkingLevel` configuration with an internal `medium` default.
14
+ - Remove implicit inheritance from the parent Agent's active model and thinking level.
15
+ - Apply task overrides before Role settings and global settings; an explicit model without `reasoning_effort` now selects that model's highest supported level.
16
+ - Clamp requested and configured levels through Pi's model-specific thinking capabilities and persist the effective value.
17
+ - Show effective thinking level alongside the model in the live tree, `/agents` picker/transcript, completion rendering, status output, and non-TUI inspection.
18
+ - Defer forwarded child permission and custom dialogs while `/agents` is open, preserving inspector keyboard focus and showing queued dialogs only after the inspector exits.
19
+
20
+ ## 0.6.0 - 2026-08-31
21
+
22
+ - Replace the single-task `spawn_agent` action with one non-redundant `spawn_agents` action whose `agents` array accepts either one or many tasks.
23
+ - Start tasks up to the configured execution limit and persist overflow tasks in an explicit FIFO `queued` state.
24
+ - Automatically start queued tasks as slots become available without blocking the parent tool call.
25
+ - Report `queued` status, queue position, and running/queued capacity through `list_agents(view="status")`, action results, the live widget, and `/agents`.
26
+ - Allow queued agents to receive persistent messages and be cancelled without loading an `AgentSession`.
27
+ - Restore waiting work after main-session resume while interrupted running work remains interrupted.
28
+ - Add `maxConcurrentSubagents` and `maxResidentSubagents` settings with validated positive-integer values.
29
+ - Keep exactly five catalog actions and the same two provider-facing gateway schemas.
30
+
31
+ ## 0.5.3 - 2026-08-31
32
+
33
+ - Align transcript navigation with `/tree`: `↑`/`↓` move by line and `←`/`→` move by page.
34
+ - Add distinct single-key jumps: `t` for the top and `b` for the bottom.
35
+ - Remove duplicate `PageUp`/`PageDown` and `Home`/`End` aliases from the transcript viewer.
36
+
37
+ ## 0.5.2 - 2026-08-30
38
+
39
+ - Show each active sub-agent's full `provider/model` identifier in the live tree below the editor.
40
+ - Preserve terminal-width truncation for long model names.
41
+
42
+ ## 0.5.1 - 2026-08-30
43
+
44
+ - Reuse each child session's original custom tool renderers in the read-only transcript viewer.
45
+ - Match the main transcript's compact `web_search`, `fetch`, and other extension-tool presentation instead of dumping raw tool content.
46
+ - Cache renderer definitions across child-session unloading and discover them without waking or loading a child when viewing restored sessions.
47
+
48
+ ## 0.5.0 - 2026-08-30
49
+
50
+ - Turn `/agents` into an interactive, user-only sub-agent session browser.
51
+ - Add read-only Pi-style transcript viewing for assistant output, thinking, tool calls, tool results, and collaboration messages.
52
+ - Support `/agents <path>` direct opening and agent-path argument completion.
53
+ - Refresh running transcripts without switching, waking, or loading child sessions.
54
+ - Keep the inspector out of all model-facing prompts, messages, and tool schemas.
55
+
56
+ ## 0.4.1 - 2026-08-15
57
+
58
+ - Remove test-only dependencies from the published package manifest so Pi Git installs stay dependency-free.
59
+ - Keep test tooling isolated in temporary development environments.
60
+
61
+ ## 0.4.0 - 2026-08-15
62
+
63
+ - Replace vague root guidance with Codex-derived parallel speed/quality criteria while keeping bounded-task guidance in the on-demand action catalog.
64
+ - Move the default child model from source code to `~/.pi/agent/codex-agents/agents-setting.json`.
65
+ - Resolve models by spawn override, role override, global setting, then parent-model inheritance.
66
+ - Decouple model selection from `fork_turns` history inheritance.
67
+ - Re-queue failed notification flushes instead of dropping them.
68
+ - Defer queued mail after aborted or errored turns without automatically restarting the recipient.
69
+ - Add regression tests for prompt separation, settings validation and precedence, notification ordering, abort behavior, and flush recovery.
70
+
71
+ ## 0.3.2 - 2026-08-15
72
+
73
+ - Queue inter-agent notifications while the recipient's turn is active instead of appending into the live message tree.
74
+ - Fix assistant(tool_calls) → user → tool history corruption that caused gateway 400 errors on strict providers.
75
+ - Deliver queued completion notices inside `wait_agent` results.
76
+ - Flush remaining queued notices at turn end and wake the recipient to process them.
77
+
78
+ ## 0.3.1 - 2026-08-15
79
+
80
+ - Group child sessions and results under their owning root session ID.
81
+ - Record the root session file in each storage group's `owner.json`.
82
+ - Remove orphaned storage groups only when an existing main session is resumed.
83
+ - Migrate the resumed main session's referenced legacy child files into its group.
84
+
85
+ ## 0.3.0 - 2026-08-15
86
+
87
+ - Replace session-persistent deferred schemas with a stable `agent_action` dispatcher.
88
+ - Add `list_agents(view="roles" | "tools" | "status" | "results")` and remove boolean view flags.
89
+ - Return the five action descriptions and parameter schemas only from the `tools` catalog view.
90
+ - Keep individual action implementations locally registered without exposing their provider schemas.
91
+ - Use the same compact gateway surface for root and child agents.
92
+
93
+ ## 0.2.1 - 2026-08-15
94
+
95
+ - Reduce the root prompt to a single delegation capability sentence.
96
+ - Move role discovery, tool activation, and result retrieval guidance into `list_agents` parameter descriptions.
97
+ - Show discovered roles and enabled tools in the `list_agents` TUI result.
98
+ - Label `list_agents` calls as roles, results, or status instead of always showing `all`.
99
+
100
+ ## 0.2.0 - 2026-08-15
101
+
102
+ - Keep only `list_agents` active before delegation is requested.
103
+ - Add `list_agents(include_roles=true)` for on-demand role discovery.
104
+ - Dynamically enable collaboration tools through Pi deferred tool loading.
105
+ - Remove hard-coded role names from the `spawn_agent` schema.
106
+ - Keep the root prompt limited to capability discovery and result retrieval.
107
+
108
+ ## 0.1.0 - 2026-08-15
109
+
110
+ - Initial public release.
111
+ - Persistent recursive agent tree backed by in-process Pi `AgentSession`s.
112
+ - Six Codex-style collaboration tools.
113
+ - Context forking, mailbox communication, concurrency and residency limits.
114
+ - Hidden child-session persistence and lazy reload.
115
+ - Root-TUI permission approval forwarding.
116
+ - Pull-based result retrieval with compact completion notices.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 smithyyang
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,190 @@
1
+ # Codex-style Agents for Pi
2
+
3
+ In-process persistent multi-agent collaboration modeled after OpenAI Codex MultiAgentV2.
4
+
5
+ ## Install
6
+
7
+ Install the public npm package:
8
+
9
+ ```bash
10
+ pi install npm:@youngjurry/pi-agents
11
+ ```
12
+
13
+ Or install directly from GitHub:
14
+
15
+ ```bash
16
+ pi install git:github.com/YoungJurry/pi-agents
17
+ ```
18
+
19
+ Choose only one source to avoid loading the extension twice. Then start Pi or run `/reload` in an existing session.
20
+
21
+ > This extension runs child agents with the same OS user and filesystem permissions as the root Pi process. Review [SECURITY.md](./SECURITY.md) before using it on untrusted work.
22
+
23
+ ## Quick start
24
+
25
+ Ask Pi to delegate work:
26
+
27
+ ```text
28
+ Spawn two agents to research independent parts of this task, wait for completion,
29
+ then pull and summarize their results.
30
+ ```
31
+
32
+ The model queries `list_agents(view="tools")` for the action catalog and, when needed, `list_agents(view="roles")` for role-specific configuration. It executes catalog actions through the compact `agent_action` dispatcher.
33
+
34
+ Completion notices are intentionally small. Full answers enter the parent context only when explicitly requested with `list_agents(view="results")`.
35
+
36
+ ## Inspect sub-agent sessions
37
+
38
+ Use the user-only `/agents` command to browse the current root session's sub-agents and open a read-only transcript:
39
+
40
+ ```text
41
+ /agents
42
+ /agents /root/api_research
43
+ ```
44
+
45
+ The picker orders agents by latest task assignment, newest first; a successful `followup_task` moves a reused agent back to the top without letting ordinary streaming or tool activity reshuffle the list. It shows lifecycle status, model, effective thinking level, role, nickname, and residency. Selecting an agent opens its active session branch with normal Pi-style assistant messages, thinking, tool calls, tool results, and collaboration messages. Custom tools reuse their original Pi renderers, so tools such as `web_search` and `fetch` stay as compact as they are in the main transcript. Running sessions refresh while the viewer is open. Child permission dialogs wait behind the inspector and appear only after the user fully exits `/agents`, so they cannot steal focus from or hide behind its overlay.
46
+
47
+ Viewer controls:
48
+
49
+ - `↑` / `↓`: move one line
50
+ - `←` / `→`: move one page up or down
51
+ - `t` / `b`: jump to the top or bottom
52
+ - `Ctrl+O`: expand or collapse tool output
53
+ - `Ctrl+T`: show or hide thinking
54
+ - `r`: refresh immediately
55
+ - `Escape`: return to the agent picker
56
+
57
+ This inspector is implemented only as a slash command and TUI overlay. It does not register an LLM tool, alter tool schemas or system prompts, add messages to the root context, switch sessions, wake agents, or expose child sessions through the normal `/resume` picker.
58
+
59
+ ## Tools
60
+
61
+ Only two compact collaboration schemas remain active:
62
+
63
+ - `list_agents`: query one catalog or data view
64
+ - `roles`: available roles and their tool access
65
+ - `tools`: action descriptions and parameter schemas
66
+ - `status`: canonical agent-tree status
67
+ - `results`: stored final answers
68
+ - `agent_action`: execute a catalog action by name with its matching arguments
69
+
70
+ `list_agents(view="tools")` returns these actions as ordinary tool-result content instead of activating more provider tool schemas:
71
+
72
+ - `spawn_agents`: submit one or many persistent child tasks in a single action; overflow waits in the visible FIFO queue
73
+ - `send_message`: non-waking mailbox message
74
+ - `followup_task`: assign more work and trigger processing
75
+ - `wait_agent`: event-driven mailbox wait
76
+ - `interrupt_agent`: abort a run without deleting context
77
+
78
+ Example dispatcher call:
79
+
80
+ ```json
81
+ {
82
+ "action": "spawn_agents",
83
+ "arguments": {
84
+ "agents": [
85
+ {
86
+ "message": "Research the API implementation",
87
+ "task_name": "api_research",
88
+ "agent_type": "explorer"
89
+ },
90
+ {
91
+ "message": "Inspect the test strategy",
92
+ "task_name": "test_research",
93
+ "agent_type": "explorer"
94
+ }
95
+ ]
96
+ }
97
+ }
98
+ ```
99
+
100
+ The five action implementations stay registered locally, but their individual provider schemas are never added to later requests. `spawn_agents` replaces the old single-task action rather than adding a redundant sixth action; an array with one item performs a single spawn. Root and child agents use the same stable `list_agents` + `agent_action` surface. Agents use canonical paths such as `/root/api_research` and can recursively spawn children.
101
+
102
+ ## Context inheritance
103
+
104
+ Each item in the `spawn_agents` action accepts a `fork_turns` argument:
105
+
106
+ - `none`: fresh context
107
+ - `all`: sanitized semantic parent context (default)
108
+ - a positive integer string: the most recent N task/user turns
109
+
110
+ Tool calls, tool results, thinking, shell transcripts, and previous collaboration mail are removed from inherited context.
111
+
112
+ ## Roles
113
+
114
+ Built-ins: `default`, `explorer`, `awaiter`.
115
+
116
+ Additional roles are read from:
117
+
118
+ - `~/.pi/agent/agents/*.md`
119
+ - nearest trusted `.pi/agents/*.md`
120
+
121
+ Role format:
122
+
123
+ ```markdown
124
+ ---
125
+ name: reviewer
126
+ description: Review code without editing
127
+ tools: read, grep, find, ls, bash
128
+ model: openai/gpt-5.4
129
+ thinking: high
130
+ nickname_candidates: [Ada, Grace]
131
+ ---
132
+
133
+ Review carefully and return findings with exact paths.
134
+ ```
135
+
136
+ ## Model configuration
137
+
138
+ Global sub-agent settings live outside the installed package so updates cannot overwrite them:
139
+
140
+ `~/.pi/agent/codex-agents/agents-setting.json`
141
+
142
+ ```json
143
+ {
144
+ "defaultModel": "opencode-go/ox-alpha-free",
145
+ "defaultThinkingLevel": "medium",
146
+ "maxConcurrentSubagents": 3,
147
+ "maxResidentSubagents": 3
148
+ }
149
+ ```
150
+
151
+ Model and thinking configuration uses task fields first, then Role frontmatter, then global settings. Parent Agent model and thinking state are never used as implicit fallbacks.
152
+
153
+ | Task override | Effective selection |
154
+ |---|---|
155
+ | `model` and `reasoning_effort` | Use both explicit values |
156
+ | `model` only | Use that model and its highest supported thinking level |
157
+ | `reasoning_effort` only | Use the Role/global model with the explicit thinking level |
158
+ | neither | Use Role values where present, then `defaultModel` and `defaultThinkingLevel` |
159
+
160
+ Unsupported thinking values are clamped through Pi's model-specific `thinkingLevelMap`, and the effective level is persisted and displayed. If `defaultThinkingLevel` is omitted, the plugin-level default is `medium`. Supported configured values are `off`, `minimal`, `low`, `medium`, `high`, `xhigh`, and `max`.
161
+
162
+ The settings file is optional, but spawning requires a model from either the task, selected Role, or `defaultModel`; it no longer inherits the parent model. Limits must be positive integers, and `maxResidentSubagents` cannot be smaller than `maxConcurrentSubagents`. If concurrency is configured while residency is omitted, residency automatically expands to at least the concurrency limit. Setting changes take effect after `/reload`. Invalid JSON, an invalid `defaultThinkingLevel`, an empty `defaultModel`, or an unavailable configured model produces an explicit error. `fork_turns` controls inherited messages independently of model and thinking selection.
163
+
164
+ ## Lifecycle
165
+
166
+ - Default child execution slots: 3 (4 active agents including root), configurable with `maxConcurrentSubagents`
167
+ - Default resident child sessions: 3, configurable with `maxResidentSubagents`
168
+ - `spawn_agents` starts tasks until all execution slots are occupied and records the remainder as `queued`
169
+ - Queued tasks are lightweight, persistent, FIFO ordered, and do not occupy resident-session capacity
170
+ - `list_agents(view="status")` reports each waiting task's queue position plus current running/queued capacity
171
+ - The live widget shows `Agents active: <running>/<limit> · queued: <waiting>` and labels waiting paths explicitly
172
+ - Completed/interrupted sessions are unloaded by LRU when residency is full
173
+ - Child sessions persist under `~/.pi/agent/codex-agents/roots/<root-session-id>/sessions/` and reload lazily
174
+ - Full final answers persist under `~/.pi/agent/codex-agents/roots/<root-session-id>/results/`
175
+ - Each root storage group records its owning main-session file in `owner.json`
176
+ - Resuming an existing main session removes groups whose owning main-session file has been deleted; new sessions and `/reload` do not trigger cleanup
177
+ - Referenced legacy flat child files are migrated when their main session is resumed
178
+ - Parents receive a compact completion notice instead of the full answer; use `list_agents(view="results")` or read the result file on demand
179
+ - Notices to a busy agent are queued safely: `wait_agent` returns them in its own result, and any leftovers are delivered right after a successful recipient turn
180
+ - Failed notice delivery is re-queued instead of silently discarded
181
+ - Notices pending when a turn is aborted or errors are deferred to the next explicit turn without restarting the interrupted agent
182
+ - The extension never inserts messages between an assistant tool call and its tool result, keeping session history protocol-valid for strict gateways
183
+ - Child sessions are kept out of Pi's normal `/resume` picker
184
+ - Agent-tree metadata persists in root session custom entries
185
+ - Child extension approval dialogs are serialized and forwarded to the root TUI with the agent path
186
+ - While `/agents` is open, forwarded permission/custom dialogs wait without taking keyboard focus and appear only after the inspector closes
187
+ - In non-interactive modes, permission extensions such as `permission-gate.ts` fail closed
188
+ - All agents share the same cwd and filesystem
189
+
190
+ Use `/agents` to browse the tree and inspect read-only child transcripts. A compact live tree appears below the editor while child agents exist and shows each active agent's `provider/model` identifier and effective thinking level.
package/SECURITY.md ADDED
@@ -0,0 +1,7 @@
1
+ # Security
2
+
3
+ This extension runs subagents in the same Pi process and operating-system user account. Agents share the working directory and filesystem permissions.
4
+
5
+ Tool allowlists and permission extensions are policy controls, not an OS sandbox. For untrusted tasks, run Pi inside a container, VM, or other system-level sandbox.
6
+
7
+ Child approval dialogs from loaded permission extensions are forwarded to the root TUI. In non-interactive modes, permission extensions such as `permission-gate.ts` can fail closed according to their own policy.
package/context.ts ADDED
@@ -0,0 +1,124 @@
1
+ import type { AgentMessage } from "@earendil-works/pi-agent-core";
2
+ import type { AssistantMessage, TextContent } from "@earendil-works/pi-ai";
3
+ import { EXTENSION_ID } from "./types.ts";
4
+
5
+ export type ForkMode = "none" | "all" | number;
6
+
7
+ const SHARED_GUIDANCE = `All agents share the same working directory and filesystem. Edits made by one agent are immediately visible to every other agent. Coordinate ownership of files and avoid overwriting another agent's work.`;
8
+
9
+ export function rootAgentInstructions(): string {
10
+ return `<multi_agent_role>You can use sub-agents when parallel work would materially improve speed or quality.</multi_agent_role>`;
11
+ }
12
+
13
+ export function childAgentInstructions(path: string, parentPath: string, rolePrompt?: string): string {
14
+ const role = rolePrompt?.trim() ? `\n\n<agent_role>\n${rolePrompt.trim()}\n</agent_role>` : "";
15
+ return `<multi_agent_role>\nYou are ${path}, a sub-agent in a collaborative team. Your direct parent is ${parentPath}. Complete the assigned task independently. You can use the collaboration tools and may spawn child agents. Your full final response is stored as the task result; your parent receives only a brief completion notification. Do not repeatedly poll or send the same result manually.\n\n${SHARED_GUIDANCE}\n</multi_agent_role>${role}`;
16
+ }
17
+
18
+ export function formatTeamMessage(options: {
19
+ type: "NEW_TASK" | "MESSAGE" | "AGENT_STATUS";
20
+ taskName: string;
21
+ sender: string;
22
+ payload: string;
23
+ }): string {
24
+ if (options.type === "AGENT_STATUS") return options.payload;
25
+ return `Message Type: ${options.type}\nTask name: ${options.taskName}\nSender: ${options.sender}\nPayload:\n${options.payload}`;
26
+ }
27
+
28
+ export function parseForkMode(value: string | undefined): ForkMode {
29
+ const normalized = value?.trim().toLowerCase() || "all";
30
+ if (normalized === "none") return "none";
31
+ if (normalized === "all") return "all";
32
+ if (!/^\d+$/.test(normalized) || Number(normalized) <= 0) {
33
+ throw new Error("fork_turns must be `none`, `all`, or a positive integer string");
34
+ }
35
+ return Number(normalized);
36
+ }
37
+
38
+ function assistantTextOnly(message: AssistantMessage): AssistantMessage | undefined {
39
+ if (message.stopReason !== "stop") return undefined;
40
+ const content = message.content
41
+ .filter((part): part is TextContent => part.type === "text")
42
+ .map((part) => ({ ...part }));
43
+ if (content.length === 0) return undefined;
44
+ return { ...message, content };
45
+ }
46
+
47
+ function isTaskBoundary(message: AgentMessage): boolean {
48
+ if (message.role === "user") return true;
49
+ if (message.role !== "custom") return false;
50
+ if (message.customType !== EXTENSION_ID) return false;
51
+ const text = typeof message.content === "string"
52
+ ? message.content
53
+ : message.content.filter((part) => part.type === "text").map((part) => part.text).join("\n");
54
+ return text.startsWith("Message Type: NEW_TASK\n");
55
+ }
56
+
57
+ function cloneMessage(message: AgentMessage): AgentMessage {
58
+ return structuredClone(message);
59
+ }
60
+
61
+ /**
62
+ * Build the semantic history inherited by a child. Tool calls/results, thinking,
63
+ * bash transcripts, and prior inter-agent mail are intentionally removed.
64
+ */
65
+ export function sanitizeForkMessages(messages: readonly AgentMessage[], mode: ForkMode): AgentMessage[] {
66
+ if (mode === "none") return [];
67
+ let selected = [...messages];
68
+ if (typeof mode === "number") {
69
+ let boundaries = 0;
70
+ let start = 0;
71
+ for (let index = selected.length - 1; index >= 0; index--) {
72
+ if (!isTaskBoundary(selected[index]!)) continue;
73
+ boundaries++;
74
+ if (boundaries === mode) {
75
+ start = index;
76
+ break;
77
+ }
78
+ }
79
+ selected = selected.slice(start);
80
+ }
81
+
82
+ const output: AgentMessage[] = [];
83
+ for (const message of selected) {
84
+ switch (message.role) {
85
+ case "user":
86
+ output.push(cloneMessage(message));
87
+ break;
88
+ case "assistant": {
89
+ const cleaned = assistantTextOnly(message);
90
+ if (cleaned) output.push(cloneMessage(cleaned));
91
+ break;
92
+ }
93
+ case "branchSummary":
94
+ case "compactionSummary":
95
+ output.push(cloneMessage(message));
96
+ break;
97
+ case "custom":
98
+ if (message.customType !== EXTENSION_ID) output.push(cloneMessage(message));
99
+ break;
100
+ default:
101
+ break;
102
+ }
103
+ }
104
+ return output;
105
+ }
106
+
107
+ export function lastAssistantAnswer(messages: readonly AgentMessage[]): { text: string; timestamp?: number; error?: string; aborted: boolean } {
108
+ for (let index = messages.length - 1; index >= 0; index--) {
109
+ const message = messages[index];
110
+ if (message?.role !== "assistant") continue;
111
+ const text = message.content
112
+ .filter((part): part is TextContent => part.type === "text")
113
+ .map((part) => part.text)
114
+ .join("\n")
115
+ .trim();
116
+ return {
117
+ text,
118
+ timestamp: message.timestamp,
119
+ error: message.stopReason === "error" ? message.errorMessage || "agent error" : undefined,
120
+ aborted: message.stopReason === "aborted",
121
+ };
122
+ }
123
+ return { text: "", aborted: false };
124
+ }