@heyhuynhgiabuu/pi-task 0.1.4 → 0.1.6

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
@@ -4,6 +4,51 @@ All notable changes to `@heyhuynhgiabuu/pi-task` are documented here.
4
4
  The format is based on [Keep a Changelog](https://keepachangelog.com/),
5
5
  and this project adheres to [Semantic Versioning](https://semver.org/).
6
6
 
7
+ ## [0.1.6] — 2026-06-25
8
+
9
+ ### Changed
10
+
11
+ - Per-task data is now in flat files at the top of `.pi/artifacts/`.
12
+ No per-task subdirs, no `<task-id>` paths. The pikit canonical
13
+ files (TODO.md, PLAN.md, PROGRESS.md, DECISIONS.md) are flat at the
14
+ same level; pi-task files now sit alongside them.
15
+ - Refined the task TUI widget and background completion rendering:
16
+ foreground/background task stats now use consistent colors, background
17
+ completion summaries use a padded themed result block, completed
18
+ background widgets no longer duplicate the main-pane completion, and
19
+ final tool-call counts now match the live widget count.
20
+
21
+ ### Layout
22
+
23
+ - `.pi/artifacts/TASKS.md` — one `### <task-id>` block per task, with
24
+ H4 subsections for `#### Metadata` (JSON) and `#### Result`.
25
+ - `.pi/artifacts/task-sessions.json` — registry mapping
26
+ `conversation_id` to `{ task_id, session_file }`. Renamed from
27
+ the v0.1.5 `task-conversations.json`.
28
+ - The subagent's session is auto-saved by pi at
29
+ `~/.pi/agent/sessions/<cwd>/<session-id>.jsonl`. pi-task reads
30
+ the last assistant message from there to populate `#### Result`
31
+ in `TASKS.md`. The subagent's final assistant message IS the
32
+ result; no separate result file is required.
33
+
34
+ ### Removed
35
+
36
+ - `.pi/artifacts/task-<id>/` per-task subdirs (and the
37
+ `metadata.json` + `SESSION.md` + `sessions/` files inside them).
38
+ All per-task data lives in `TASKS.md` blocks now.
39
+ - `.pi/artifacts/task-conversations.json` — replaced by
40
+ `task-sessions.json`.
41
+ - The `taskArtifactName(taskId)` / `taskIdFromArtifactName(name)`
42
+ helpers and the `getArtifactsDir(piDir)` / `getTaskDir(piDir)` /
43
+ `getTaskRunsDir(piDir)` helpers.
44
+
45
+ ### Verified
46
+
47
+ - `npm test` passes
48
+ - `npm run typecheck` passes
49
+ - `npm run build` passes
50
+ - `npm run smoke` passes
51
+
7
52
  ## [0.1.4] — 2026-06-21
8
53
 
9
54
  ### Fixed
@@ -76,7 +121,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/).
76
121
  - `npm view @heyhuynhgiabuu/pi-task@0.1.2 pi` returns
77
122
  `{ extensions: [ './dist/index.js' ] }`
78
123
 
79
- [0.1.2]: https://github.com/buddingnewinsights/pi-task/releases/tag/v0.1.2
124
+ [0.1.2]: https://github.com/heyhuynhgiabuu/pi-task/releases/tag/v0.1.2
80
125
 
81
126
  ## [0.1.1] — 2025
82
127
 
@@ -116,6 +161,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/).
116
161
 
117
162
  See the git history: `git log --oneline -- CHANGELOG.md`.
118
163
 
119
- [0.1.1]: https://github.com/buddingnewinsights/pi-task/releases/tag/v0.1.1
120
- [Keep a Changelog]: https://keepachangelog.com/
121
- [Semantic Versioning]: https://semver.org/
164
+ [0.1.1]: https://github.com/heyhuynhgiabuu/pi-task/releases/tag/v0.1.1
165
+ [0.1.4]: https://github.com/heyhuynhgiabuu/pi-task/releases/tag/v0.1.4
166
+ [0.1.5]: https://github.com/heyhuynhgiabuu/pi-task/releases/tag/v0.1.5
167
+ [0.1.6]: https://github.com/heyhuynhgiabuu/pi-task/releases/tag/v0.1.6
168
+ [Keep a Changelog]: https://keepachangelog.com/
169
+ [Semantic Versioning]: https://semver.org/
package/README.md CHANGED
@@ -45,7 +45,7 @@ Foreground task:
45
45
 
46
46
  Background task:
47
47
 
48
- ```json
48
+ ```
49
49
  {
50
50
  "agent_type": "scout",
51
51
  "description": "Research SDK docs",
@@ -54,6 +54,38 @@ Background task:
54
54
  }
55
55
  ```
56
56
 
57
+ Durable specialist conversation:
58
+
59
+ ```
60
+ {
61
+ "agent_type": "scout",
62
+ "conversation_id": "research-ai",
63
+ "description": "Ask research assistant",
64
+ "background": false,
65
+ "prompt": "Continue our prior research thread. What did we conclude about retrieval evaluation?"
66
+ }
67
+ ```
68
+
69
+ `conversation_id` maps to a durable subagent run. Reused across calls
70
+ to keep specialist memory, e.g. a reusable research assistant.
71
+ Use `/task-sessions` to list known durable conversations.
72
+
73
+ Stored files (all flat at the top of `.pi/artifacts/`, no
74
+ per-task subdirs):
75
+
76
+ ```
77
+ .pi/artifacts/TASKS.md # one ### <task-id> block per task
78
+ .pi/artifacts/task-sessions.json # conversation_id -> { task_id, session_file }
79
+ ```
80
+
81
+ The subagent's session is auto-saved by pi at
82
+ `~/.pi/agent/sessions/<cwd>/<session-id>.jsonl`. pi-task reads
83
+ the last assistant message from there to populate
84
+ `#### Result` in `TASKS.md`. The subagent's final message IS
85
+ the result; no separate result file is required.
86
+
87
+ Note: true conversation resume requires the tmux/CLI backend so Pi can reopen the saved subagent session. SDK fallback can run one-shot tasks, but it cannot resume a prior Pi session.
88
+
57
89
  ## Agent precedence
58
90
 
59
91
  When two agents have the same name, later sources override earlier ones:
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Conversational subagent helpers.
3
+ *
4
+ * Per-task data lives in `.pi/artifacts/TASKS.md` as `### <task-id>` blocks.
5
+ * A small `task-sessions.json` registry in the same directory maps
6
+ * `conversation_id` to the auto-saved session file path so the
7
+ * subagent can be resumed later.
8
+ *
9
+ * The subagent's session is auto-saved by pi at
10
+ * `~/.pi/agent/sessions/<cwd>/<session-id>.jsonl`. pi-task does not
11
+ * maintain its own session storage.
12
+ *
13
+ * All artifacts live flat at the top of `.pi/artifacts/`, alongside the
14
+ * pikit canonical files (TODO.md, PLAN.md, PROGRESS.md, DECISIONS.md).
15
+ * No subdirs. No per-task paths.
16
+ */
17
+ export declare const TASKS_FILE = "TASKS.md";
18
+ export declare const TASK_SESSIONS_REGISTRY_FILE = "task-sessions.json";
19
+ export interface ConversationMetadata {
20
+ conversation_id: string;
21
+ task_id: string;
22
+ agent_type: string;
23
+ session_file: string;
24
+ created_at: string;
25
+ last_used_at: string;
26
+ last_prompt?: string;
27
+ }
28
+ export type TaskSessionsRegistry = Record<string, {
29
+ task_id: string;
30
+ session_file: string;
31
+ }>;
32
+ export declare function getTasksFilePath(piDir: string): string;
33
+ export declare function getTaskSessionsRegistryPath(piDir: string): string;
34
+ export declare function normalizeConversationId(value: unknown): string | undefined;
35
+ export declare function readTaskSessionsRegistry(piDir: string): TaskSessionsRegistry;
36
+ export declare function writeTaskSessionsRegistry(piDir: string, registry: TaskSessionsRegistry): void;
37
+ /**
38
+ * Find a `### <task-id>` block in TASKS.md. Returns the block content
39
+ * (everything between the heading and the next H3 or EOF) plus the
40
+ * status line if present. Returns undefined if no block exists.
41
+ */
42
+ export declare function readTaskBlock(piDir: string, taskId: string): {
43
+ status: string | null;
44
+ body: string;
45
+ } | undefined;
46
+ export declare function listTaskBlocks(piDir: string): Map<string, {
47
+ status: string | null;
48
+ body: string;
49
+ }>;
50
+ /**
51
+ * Append or update a `### <task-id>` block in TASKS.md. If the block
52
+ * already exists, its body is replaced. Otherwise, the block is
53
+ * appended at the end of the file.
54
+ */
55
+ export declare function writeTaskBlock(options: {
56
+ piDir: string;
57
+ taskId: string;
58
+ status: "active" | "done" | "abandoned";
59
+ updated: string;
60
+ body: string;
61
+ }): void;
62
+ export declare function parseMetadataFromBody(body: string | undefined): {
63
+ created_at?: string;
64
+ last_used_at?: string;
65
+ agent_type?: string;
66
+ session_file?: string;
67
+ conversation_id?: string;
68
+ last_prompt?: string;
69
+ } | undefined;
70
+ export interface WriteTaskBlockInput {
71
+ piDir: string;
72
+ taskId: string;
73
+ conversationId: string;
74
+ agentType: string;
75
+ sessionFile: string;
76
+ prompt: string;
77
+ result: string;
78
+ resultLabel?: string;
79
+ }
80
+ /**
81
+ * Persist a completed task: write (or update) the `### <task-id>` block
82
+ * in TASKS.md with metadata and result as H4 subsections. Also updates
83
+ * the task-sessions registry.
84
+ */
85
+ export declare function writeConversationArtifacts(input: WriteTaskBlockInput): ConversationMetadata;
86
+ export declare function renderConversationSessions(piDir: string): string;
@@ -0,0 +1,238 @@
1
+ import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ /**
4
+ * Conversational subagent helpers.
5
+ *
6
+ * Per-task data lives in `.pi/artifacts/TASKS.md` as `### <task-id>` blocks.
7
+ * A small `task-sessions.json` registry in the same directory maps
8
+ * `conversation_id` to the auto-saved session file path so the
9
+ * subagent can be resumed later.
10
+ *
11
+ * The subagent's session is auto-saved by pi at
12
+ * `~/.pi/agent/sessions/<cwd>/<session-id>.jsonl`. pi-task does not
13
+ * maintain its own session storage.
14
+ *
15
+ * All artifacts live flat at the top of `.pi/artifacts/`, alongside the
16
+ * pikit canonical files (TODO.md, PLAN.md, PROGRESS.md, DECISIONS.md).
17
+ * No subdirs. No per-task paths.
18
+ */
19
+ export const TASKS_FILE = "TASKS.md";
20
+ export const TASK_SESSIONS_REGISTRY_FILE = "task-sessions.json";
21
+ export function getTasksFilePath(piDir) {
22
+ return join(piDir, "artifacts", TASKS_FILE);
23
+ }
24
+ export function getTaskSessionsRegistryPath(piDir) {
25
+ return join(piDir, "artifacts", TASK_SESSIONS_REGISTRY_FILE);
26
+ }
27
+ export function normalizeConversationId(value) {
28
+ if (typeof value !== "string")
29
+ return undefined;
30
+ const conversationId = value.trim();
31
+ if (!conversationId)
32
+ return undefined;
33
+ if (!/^[A-Za-z0-9._-]{1,80}$/.test(conversationId)) {
34
+ throw new Error("conversation_id must be 1-80 chars and contain only letters, numbers, '.', '_' or '-'");
35
+ }
36
+ return conversationId;
37
+ }
38
+ export function readTaskSessionsRegistry(piDir) {
39
+ try {
40
+ const parsed = JSON.parse(readFileSync(getTaskSessionsRegistryPath(piDir), "utf-8"));
41
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
42
+ return {};
43
+ }
44
+ const registry = {};
45
+ for (const [key, value] of Object.entries(parsed)) {
46
+ if (value &&
47
+ typeof value === "object" &&
48
+ typeof value.task_id === "string" &&
49
+ typeof value.session_file === "string") {
50
+ const v = value;
51
+ registry[key] = { task_id: v.task_id, session_file: v.session_file };
52
+ }
53
+ }
54
+ return registry;
55
+ }
56
+ catch {
57
+ return {};
58
+ }
59
+ }
60
+ export function writeTaskSessionsRegistry(piDir, registry) {
61
+ mkdirSync(join(piDir, "artifacts"), { recursive: true });
62
+ writeFileSync(getTaskSessionsRegistryPath(piDir), `${JSON.stringify(registry, null, 2)}\n`, "utf-8");
63
+ }
64
+ /**
65
+ * Find a `### <task-id>` block in TASKS.md. Returns the block content
66
+ * (everything between the heading and the next H3 or EOF) plus the
67
+ * status line if present. Returns undefined if no block exists.
68
+ */
69
+ export function readTaskBlock(piDir, taskId) {
70
+ let content;
71
+ try {
72
+ content = readFileSync(getTasksFilePath(piDir), "utf-8");
73
+ }
74
+ catch {
75
+ return undefined;
76
+ }
77
+ return parseTaskBlocks(content).get(taskId);
78
+ }
79
+ export function listTaskBlocks(piDir) {
80
+ let content;
81
+ try {
82
+ content = readFileSync(getTasksFilePath(piDir), "utf-8");
83
+ }
84
+ catch {
85
+ return new Map();
86
+ }
87
+ return parseTaskBlocks(content);
88
+ }
89
+ function parseTaskBlocks(content) {
90
+ const blocks = new Map();
91
+ const lines = content.split("\n");
92
+ let currentTaskId = null;
93
+ let currentStatus = null;
94
+ let currentBody = [];
95
+ const flush = () => {
96
+ if (currentTaskId !== null) {
97
+ blocks.set(currentTaskId, {
98
+ status: currentStatus,
99
+ body: currentBody.join("\n"),
100
+ });
101
+ }
102
+ currentTaskId = null;
103
+ currentStatus = null;
104
+ currentBody = [];
105
+ };
106
+ for (const line of lines) {
107
+ const heading = line.match(/^###\s+(\S+)\s*$/);
108
+ if (heading) {
109
+ flush();
110
+ currentTaskId = heading[1];
111
+ continue;
112
+ }
113
+ if (currentTaskId === null)
114
+ continue;
115
+ const statusMatch = line.match(/^status:\s*(\S+)/);
116
+ if (statusMatch) {
117
+ currentStatus = statusMatch[1].toLowerCase();
118
+ continue;
119
+ }
120
+ currentBody.push(line);
121
+ }
122
+ flush();
123
+ return blocks;
124
+ }
125
+ /**
126
+ * Append or update a `### <task-id>` block in TASKS.md. If the block
127
+ * already exists, its body is replaced. Otherwise, the block is
128
+ * appended at the end of the file.
129
+ */
130
+ export function writeTaskBlock(options) {
131
+ const path = getTasksFilePath(options.piDir);
132
+ let content = "";
133
+ try {
134
+ content = readFileSync(path, "utf-8");
135
+ if (!content.endsWith("\n"))
136
+ content += "\n";
137
+ }
138
+ catch {
139
+ content = "";
140
+ }
141
+ const heading = `### ${options.taskId}`;
142
+ const statusLine = `status: ${options.status} | updated: ${options.updated}`;
143
+ const block = `${heading}\n${statusLine}\n\n${options.body}\n`;
144
+ const headingRe = new RegExp(`^### ${escapeRegExp(options.taskId)}\\s*$`, "m");
145
+ const match = content.match(headingRe);
146
+ if (match && match.index !== undefined) {
147
+ const start = match.index;
148
+ const after = content.slice(start);
149
+ const nextHeading = after.search(/^###\s+\S+/m);
150
+ const end = nextHeading > 0 ? start + nextHeading : content.length;
151
+ content = content.slice(0, start) + block + content.slice(end);
152
+ }
153
+ else {
154
+ if (content.length > 0 && !content.endsWith("\n\n")) {
155
+ content += "\n";
156
+ }
157
+ content += block;
158
+ }
159
+ mkdirSync(join(options.piDir, "artifacts"), { recursive: true });
160
+ writeFileSync(path, content, "utf-8");
161
+ }
162
+ function escapeRegExp(value) {
163
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
164
+ }
165
+ export function parseMetadataFromBody(body) {
166
+ if (!body)
167
+ return undefined;
168
+ const match = body.match(/```json\n([\s\S]*?)\n```/);
169
+ if (!match)
170
+ return undefined;
171
+ try {
172
+ return JSON.parse(match[1]);
173
+ }
174
+ catch {
175
+ return undefined;
176
+ }
177
+ }
178
+ /**
179
+ * Persist a completed task: write (or update) the `### <task-id>` block
180
+ * in TASKS.md with metadata and result as H4 subsections. Also updates
181
+ * the task-sessions registry.
182
+ */
183
+ export function writeConversationArtifacts(input) {
184
+ const now = new Date().toISOString();
185
+ const existing = readTaskBlock(input.piDir, input.taskId);
186
+ const previous = parseMetadataFromBody(existing?.body);
187
+ const metadata = {
188
+ conversation_id: input.conversationId,
189
+ task_id: input.taskId,
190
+ agent_type: input.agentType,
191
+ session_file: input.sessionFile,
192
+ created_at: previous?.created_at ?? now,
193
+ last_used_at: now,
194
+ last_prompt: input.prompt,
195
+ };
196
+ const body = [
197
+ "#### Metadata",
198
+ "",
199
+ "```json",
200
+ JSON.stringify(metadata, null, 2),
201
+ "```",
202
+ "",
203
+ "#### Result",
204
+ "",
205
+ input.result.trim(),
206
+ "",
207
+ ].join("\n");
208
+ writeTaskBlock({
209
+ piDir: input.piDir,
210
+ taskId: input.taskId,
211
+ status: "done",
212
+ updated: now,
213
+ body,
214
+ });
215
+ const registry = readTaskSessionsRegistry(input.piDir);
216
+ registry[input.conversationId] = {
217
+ task_id: input.taskId,
218
+ session_file: input.sessionFile,
219
+ };
220
+ writeTaskSessionsRegistry(input.piDir, registry);
221
+ return metadata;
222
+ }
223
+ export function renderConversationSessions(piDir) {
224
+ const blocks = listTaskBlocks(piDir);
225
+ if (blocks.size === 0) {
226
+ return 'No durable task conversations found. Start one with task({ conversation_id: "research-ai", ... }).';
227
+ }
228
+ const entries = Array.from(blocks.entries()).sort(([a], [b]) => a.localeCompare(b));
229
+ const lines = ["Durable task conversations:"];
230
+ for (const [taskId, block] of entries) {
231
+ const metadata = parseMetadataFromBody(block.body);
232
+ const agent = metadata?.agent_type ?? "unknown";
233
+ const last = metadata?.last_used_at ?? "unknown";
234
+ const conv = metadata?.conversation_id ?? "(no conversation_id)";
235
+ lines.push(`${conv} -> ${taskId} — ${agent}, last used ${last}`);
236
+ }
237
+ return lines.join("\n");
238
+ }
package/dist/helpers.d.ts CHANGED
@@ -72,12 +72,12 @@ export declare function formatAgentList(agents: AgentConfig[]): string;
72
72
  * Build pi CLI arguments for spawning or resuming a sub-agent session.
73
73
  *
74
74
  * - Fresh spawn: omit `resume` or pass falsy — `--session` is not included.
75
- * - Resume: pass `resume=true` `--session <name>` is included so pi
76
- * continues the existing session file in --session-dir.
77
- */
78
- export declare function buildPiArgs(agent: AgentConfig, sessionName: string, sessionDir: string, promptContent: string, resume?: boolean, parentToolNames?: string[]): string[];
75
+ * - Resume: pass `resume=true` and optionally `resumeSessionRef`
76
+ * `--session <ref>` is included so pi continues an existing session.
77
+ */
78
+ export declare function buildPiArgs(agent: AgentConfig, sessionName: string, sessionDir: string, promptContent: string, resume?: boolean, parentToolNames?: string[], resumeSessionRef?: string): string[];
79
79
  /** Count tool uses and turns from pi JSONL session files. */
80
- export declare function countToolUses(sessionDir: string): {
80
+ export declare function countToolUses(sessionDir: string, sessionName?: string): {
81
81
  toolUses: number;
82
82
  turns: number;
83
83
  };
@@ -94,7 +94,7 @@ export declare function summarizeArgs(toolName: string, args: unknown): string;
94
94
  * Returns total counts plus the last `limit` records in chronological order.
95
95
  * Safe against malformed lines and missing fields.
96
96
  */
97
- export declare function readRecentToolCalls(sessionDir: string, limit?: number): {
97
+ export declare function readRecentToolCalls(sessionDir: string, limit?: number, sessionName?: string): {
98
98
  toolUses: number;
99
99
  turns: number;
100
100
  recent: ToolCallRecord[];
package/dist/helpers.js CHANGED
@@ -256,22 +256,42 @@ export function formatAgentList(agents) {
256
256
  * Build pi CLI arguments for spawning or resuming a sub-agent session.
257
257
  *
258
258
  * - Fresh spawn: omit `resume` or pass falsy — `--session` is not included.
259
- * - Resume: pass `resume=true` `--session <name>` is included so pi
260
- * continues the existing session file in --session-dir.
261
- */
262
- export function buildPiArgs(agent, sessionName, sessionDir, promptContent, resume, parentToolNames) {
259
+ * - Resume: pass `resume=true` and optionally `resumeSessionRef`
260
+ * `--session <ref>` is included so pi continues an existing session.
261
+ */
262
+ export function buildPiArgs(agent, sessionName, sessionDir, promptContent, resume, parentToolNames, resumeSessionRef) {
263
263
  return buildPiArgv({
264
264
  agent,
265
265
  sessionName,
266
266
  sessionDir,
267
267
  promptContent,
268
268
  resume,
269
+ resumeSessionRef,
269
270
  parentToolNames,
270
271
  });
271
272
  }
272
273
  // ─── JSONL Session Helpers ───────────────────────────────────────────────────
274
+ function matchesJsonlSessionName(content, sessionName) {
275
+ if (!sessionName)
276
+ return true;
277
+ for (const rawLine of content.split("\n")) {
278
+ const line = rawLine.trim();
279
+ if (!line)
280
+ continue;
281
+ try {
282
+ const entry = JSON.parse(line);
283
+ if (entry.type === "session_info") {
284
+ return (entry.name ?? entry.session_info?.name) === sessionName;
285
+ }
286
+ }
287
+ catch {
288
+ // Skip malformed lines
289
+ }
290
+ }
291
+ return false;
292
+ }
273
293
  /** Count tool uses and turns from pi JSONL session files. */
274
- export function countToolUses(sessionDir) {
294
+ export function countToolUses(sessionDir, sessionName) {
275
295
  let toolUses = 0;
276
296
  let turns = 0;
277
297
  try {
@@ -280,6 +300,8 @@ export function countToolUses(sessionDir) {
280
300
  const files = readdirSync(sessionDir).filter((f) => f.endsWith(".jsonl"));
281
301
  for (const file of files) {
282
302
  const content = readFileSync(join(sessionDir, file), "utf-8");
303
+ if (!matchesJsonlSessionName(content, sessionName))
304
+ continue;
283
305
  for (const rawLine of content.split("\n")) {
284
306
  const line = rawLine.trim();
285
307
  if (!line)
@@ -368,7 +390,7 @@ export function summarizeArgs(toolName, args) {
368
390
  * Returns total counts plus the last `limit` records in chronological order.
369
391
  * Safe against malformed lines and missing fields.
370
392
  */
371
- export function readRecentToolCalls(sessionDir, limit = 12) {
393
+ export function readRecentToolCalls(sessionDir, limit = 12, sessionName) {
372
394
  let toolUses = 0;
373
395
  let turns = 0;
374
396
  const calls = [];
@@ -379,6 +401,8 @@ export function readRecentToolCalls(sessionDir, limit = 12) {
379
401
  const files = readdirSync(sessionDir).filter((f) => f.endsWith(".jsonl"));
380
402
  for (const file of files) {
381
403
  const content = readFileSync(join(sessionDir, file), "utf-8");
404
+ if (!matchesJsonlSessionName(content, sessionName))
405
+ continue;
382
406
  for (const rawLine of content.split("\n")) {
383
407
  const line = rawLine.trim();
384
408
  if (!line)
package/dist/index.d.ts CHANGED
@@ -15,4 +15,21 @@
15
15
  * detection, 30-minute timeout.
16
16
  */
17
17
  import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
18
+ export /** Details attached to tool result for rendering. */ interface TaskDetails {
19
+ task_id: string;
20
+ agent_type: string;
21
+ description: string;
22
+ conversation_id?: string;
23
+ phase: "done" | "timeout" | "aborted" | "failed";
24
+ status?: string;
25
+ summary?: string;
26
+ findings?: string;
27
+ evidence?: string;
28
+ confidence?: string;
29
+ duration_ms?: number;
30
+ turn_count?: number;
31
+ tool_uses?: number;
32
+ background?: boolean;
33
+ tmux_session?: string;
34
+ }
18
35
  export default function (pi: ExtensionAPI): void;