@heyhuynhgiabuu/pi-task 0.1.4 → 0.1.5
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 +28 -1
- package/dist/conversation.d.ts +39 -0
- package/dist/conversation.js +123 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.js +161 -6
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -45,7 +45,7 @@ Foreground task:
|
|
|
45
45
|
|
|
46
46
|
Background task:
|
|
47
47
|
|
|
48
|
-
```
|
|
48
|
+
```
|
|
49
49
|
{
|
|
50
50
|
"agent_type": "scout",
|
|
51
51
|
"description": "Research SDK docs",
|
|
@@ -54,6 +54,33 @@ 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 one existing `task-<id>` artifact under `.pi/artifacts/` and reuses its `sessions/` directory on later calls. This is for scoped specialist memory, e.g. a reusable research assistant. Use `/task-sessions` to list known durable conversations.
|
|
70
|
+
|
|
71
|
+
Stored files:
|
|
72
|
+
|
|
73
|
+
```
|
|
74
|
+
.pi/artifacts/task-registry.json
|
|
75
|
+
.pi/artifacts/task-<id>/CONTEXT.md
|
|
76
|
+
.pi/artifacts/task-<id>/RESULT.md
|
|
77
|
+
.pi/artifacts/task-<id>/SESSION.md
|
|
78
|
+
.pi/artifacts/task-<id>/metadata.json
|
|
79
|
+
.pi/artifacts/task-<id>/sessions/
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
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.
|
|
83
|
+
|
|
57
84
|
## Agent precedence
|
|
58
85
|
|
|
59
86
|
When two agents have the same name, later sources override earlier ones:
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Conversational subagent helpers.
|
|
3
|
+
*
|
|
4
|
+
* Durable subagent conversations reuse the existing
|
|
5
|
+
* `.pi/artifacts/task-<id>/` artifact convention and add a small
|
|
6
|
+
* `conversation_id` -> `task-<id>` registry under the same artifacts dir.
|
|
7
|
+
*/
|
|
8
|
+
export interface ConversationMetadata {
|
|
9
|
+
conversation_id: string;
|
|
10
|
+
task_id: string;
|
|
11
|
+
artifact: string;
|
|
12
|
+
agent_type: string;
|
|
13
|
+
session_dir: string;
|
|
14
|
+
session_name: string;
|
|
15
|
+
created_at: string;
|
|
16
|
+
last_used_at: string;
|
|
17
|
+
last_prompt?: string;
|
|
18
|
+
}
|
|
19
|
+
export type ConversationRegistry = Record<string, string>;
|
|
20
|
+
export declare const CONVERSATION_REGISTRY_FILE = "task-conversations.json";
|
|
21
|
+
export declare function getArtifactsDir(piDir: string): string;
|
|
22
|
+
export declare function getConversationRegistryPath(piDir: string): string;
|
|
23
|
+
export declare function taskArtifactName(taskId: string): string;
|
|
24
|
+
export declare function taskIdFromArtifactName(artifactName: string): string;
|
|
25
|
+
export declare function normalizeConversationId(value: unknown): string | undefined;
|
|
26
|
+
export declare function readConversationRegistry(piDir: string): ConversationRegistry;
|
|
27
|
+
export declare function writeConversationRegistry(piDir: string, registry: ConversationRegistry): void;
|
|
28
|
+
export declare function readConversationMetadata(metadataPath: string): ConversationMetadata | undefined;
|
|
29
|
+
export declare function buildSessionCard(metadata: ConversationMetadata): string;
|
|
30
|
+
export declare function writeConversationArtifacts(options: {
|
|
31
|
+
taskDir: string;
|
|
32
|
+
taskId: string;
|
|
33
|
+
conversationId: string;
|
|
34
|
+
agentType: string;
|
|
35
|
+
sessionDir: string;
|
|
36
|
+
sessionName: string;
|
|
37
|
+
prompt: string;
|
|
38
|
+
}): ConversationMetadata;
|
|
39
|
+
export declare function renderConversationSessions(piDir: string): string;
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
export const CONVERSATION_REGISTRY_FILE = "task-conversations.json";
|
|
4
|
+
export function getArtifactsDir(piDir) {
|
|
5
|
+
return join(piDir, "artifacts");
|
|
6
|
+
}
|
|
7
|
+
export function getConversationRegistryPath(piDir) {
|
|
8
|
+
return join(getArtifactsDir(piDir), CONVERSATION_REGISTRY_FILE);
|
|
9
|
+
}
|
|
10
|
+
export function taskArtifactName(taskId) {
|
|
11
|
+
return taskId.startsWith("task-") ? taskId : `task-${taskId}`;
|
|
12
|
+
}
|
|
13
|
+
export function taskIdFromArtifactName(artifactName) {
|
|
14
|
+
return artifactName.startsWith("task-")
|
|
15
|
+
? artifactName.slice("task-".length)
|
|
16
|
+
: artifactName;
|
|
17
|
+
}
|
|
18
|
+
export function normalizeConversationId(value) {
|
|
19
|
+
if (typeof value !== "string")
|
|
20
|
+
return undefined;
|
|
21
|
+
const conversationId = value.trim();
|
|
22
|
+
if (!conversationId)
|
|
23
|
+
return undefined;
|
|
24
|
+
if (!/^[A-Za-z0-9._-]{1,80}$/.test(conversationId)) {
|
|
25
|
+
throw new Error("conversation_id must be 1-80 chars and contain only letters, numbers, '.', '_' or '-'");
|
|
26
|
+
}
|
|
27
|
+
return conversationId;
|
|
28
|
+
}
|
|
29
|
+
export function readConversationRegistry(piDir) {
|
|
30
|
+
try {
|
|
31
|
+
const parsed = JSON.parse(readFileSync(getConversationRegistryPath(piDir), "utf-8"));
|
|
32
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
33
|
+
return {};
|
|
34
|
+
}
|
|
35
|
+
const registry = {};
|
|
36
|
+
for (const [key, value] of Object.entries(parsed)) {
|
|
37
|
+
if (typeof value === "string")
|
|
38
|
+
registry[key] = value;
|
|
39
|
+
}
|
|
40
|
+
return registry;
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
return {};
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
export function writeConversationRegistry(piDir, registry) {
|
|
47
|
+
const artifactsDir = getArtifactsDir(piDir);
|
|
48
|
+
mkdirSync(artifactsDir, { recursive: true });
|
|
49
|
+
writeFileSync(getConversationRegistryPath(piDir), `${JSON.stringify(registry, null, 2)}\n`, "utf-8");
|
|
50
|
+
}
|
|
51
|
+
export function readConversationMetadata(metadataPath) {
|
|
52
|
+
try {
|
|
53
|
+
const parsed = JSON.parse(readFileSync(metadataPath, "utf-8"));
|
|
54
|
+
if (!parsed.conversation_id || !parsed.task_id)
|
|
55
|
+
return undefined;
|
|
56
|
+
return parsed;
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
return undefined;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
export function buildSessionCard(metadata) {
|
|
63
|
+
return [
|
|
64
|
+
`# ${metadata.conversation_id}`,
|
|
65
|
+
"",
|
|
66
|
+
`Agent: ${metadata.agent_type}`,
|
|
67
|
+
`Task: ${taskArtifactName(metadata.task_id)}`,
|
|
68
|
+
`Last used: ${metadata.last_used_at}`,
|
|
69
|
+
`Session dir: ${metadata.session_dir}`,
|
|
70
|
+
"",
|
|
71
|
+
"## Resume",
|
|
72
|
+
"",
|
|
73
|
+
"```json",
|
|
74
|
+
JSON.stringify({
|
|
75
|
+
agent_type: metadata.agent_type,
|
|
76
|
+
conversation_id: metadata.conversation_id,
|
|
77
|
+
prompt: "Continue from the prior specialist conversation.",
|
|
78
|
+
}, null, 2),
|
|
79
|
+
"```",
|
|
80
|
+
"",
|
|
81
|
+
"## Last prompt",
|
|
82
|
+
"",
|
|
83
|
+
metadata.last_prompt ?? "",
|
|
84
|
+
"",
|
|
85
|
+
].join("\n");
|
|
86
|
+
}
|
|
87
|
+
export function writeConversationArtifacts(options) {
|
|
88
|
+
const now = new Date().toISOString();
|
|
89
|
+
const metadataPath = join(options.taskDir, "metadata.json");
|
|
90
|
+
const existing = readConversationMetadata(metadataPath);
|
|
91
|
+
const metadata = {
|
|
92
|
+
conversation_id: options.conversationId,
|
|
93
|
+
task_id: options.taskId,
|
|
94
|
+
artifact: taskArtifactName(options.taskId),
|
|
95
|
+
agent_type: options.agentType,
|
|
96
|
+
session_dir: options.sessionDir,
|
|
97
|
+
session_name: options.sessionName,
|
|
98
|
+
created_at: existing?.created_at ?? now,
|
|
99
|
+
last_used_at: now,
|
|
100
|
+
last_prompt: options.prompt,
|
|
101
|
+
};
|
|
102
|
+
mkdirSync(options.taskDir, { recursive: true });
|
|
103
|
+
writeFileSync(metadataPath, `${JSON.stringify(metadata, null, 2)}\n`, "utf-8");
|
|
104
|
+
writeFileSync(join(options.taskDir, "SESSION.md"), buildSessionCard(metadata), "utf-8");
|
|
105
|
+
return metadata;
|
|
106
|
+
}
|
|
107
|
+
export function renderConversationSessions(piDir) {
|
|
108
|
+
const registry = readConversationRegistry(piDir);
|
|
109
|
+
const entries = Object.entries(registry).sort(([left], [right]) => left.localeCompare(right));
|
|
110
|
+
if (entries.length === 0) {
|
|
111
|
+
return 'No durable task conversations found. Start one with task({ conversation_id: "research-ai", ... }).';
|
|
112
|
+
}
|
|
113
|
+
const lines = ["Durable task conversations:"];
|
|
114
|
+
for (const [conversationId, artifactName] of entries) {
|
|
115
|
+
const taskId = taskIdFromArtifactName(artifactName);
|
|
116
|
+
const metadata = readConversationMetadata(join(getArtifactsDir(piDir), taskArtifactName(taskId), "metadata.json"));
|
|
117
|
+
const suffix = metadata
|
|
118
|
+
? ` — ${metadata.agent_type}, last used ${metadata.last_used_at}`
|
|
119
|
+
: "";
|
|
120
|
+
lines.push(`${conversationId} -> ${taskArtifactName(taskId)}${suffix}`);
|
|
121
|
+
}
|
|
122
|
+
return lines.join("\n");
|
|
123
|
+
}
|
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;
|
package/dist/index.js
CHANGED
|
@@ -21,6 +21,7 @@ import { randomUUID } from "node:crypto";
|
|
|
21
21
|
import { dirname, join } from "node:path";
|
|
22
22
|
import { fileURLToPath } from "node:url";
|
|
23
23
|
import { Type } from "@sinclair/typebox";
|
|
24
|
+
import { getArtifactsDir, normalizeConversationId, readConversationMetadata, readConversationRegistry, renderConversationSessions, taskArtifactName, taskIdFromArtifactName, writeConversationArtifacts, writeConversationRegistry, } from "./conversation.js";
|
|
24
25
|
import { Text, truncateToWidth } from "@earendil-works/pi-tui";
|
|
25
26
|
import { TASK_BACKGROUND_DEFAULT, TASK_RESULT_XML_INSTRUCTIONS, TASK_TOOL_DESCRIPTION, buildTmuxSplitWindowArgs, chooseTmuxSplitDirection, formatBackgroundReceipt, buildPiArgs, parseResultXml, formatMs, shellQuote, discoverAgents, formatAgentList, countToolUses, readRecentToolCalls, } from "./helpers.js";
|
|
26
27
|
import { runSdkSubagent } from "./subagent/runSdk.js";
|
|
@@ -31,7 +32,7 @@ const BUNDLED_AGENT_DIR = join(dirname(fileURLToPath(import.meta.url)), "..", "a
|
|
|
31
32
|
const BACKGROUND_CHECK_MS = 10_000; // poll every 10 sec
|
|
32
33
|
const COUNT_POLL_MS = 3_000; // update toolcall counts every 3 sec
|
|
33
34
|
const TASK_TIMEOUT_MS = 30 * 60 * 1_000; // 30 minutes
|
|
34
|
-
//
|
|
35
|
+
// Conversation helpers live in ./conversation.js.
|
|
35
36
|
function readRegistry(piDir) {
|
|
36
37
|
const path = join(piDir, "task-registry.json");
|
|
37
38
|
try {
|
|
@@ -190,6 +191,7 @@ export default function (pi) {
|
|
|
190
191
|
startedAt: entry.startedAt,
|
|
191
192
|
toolUses: 0,
|
|
192
193
|
turns: 0,
|
|
194
|
+
conversationId: entry.conversationId,
|
|
193
195
|
recentCalls: [],
|
|
194
196
|
};
|
|
195
197
|
backgroundTasks.set(entry.id, bgtask);
|
|
@@ -478,7 +480,10 @@ export default function (pi) {
|
|
|
478
480
|
description: "A short (3-5 word) summary of the task",
|
|
479
481
|
}),
|
|
480
482
|
task_id: Type.Optional(Type.String({
|
|
481
|
-
description: "Resume
|
|
483
|
+
description: "Resume an existing background task by id instead of starting a new task.",
|
|
484
|
+
})),
|
|
485
|
+
conversation_id: Type.Optional(Type.String({
|
|
486
|
+
description: "Durable specialist conversation id. Reuses .pi/artifacts/task-<id>/sessions when called again.",
|
|
482
487
|
})),
|
|
483
488
|
background: Type.Optional(Type.Boolean({
|
|
484
489
|
description: "Run in background (async). You will be notified when it completes. DO NOT sleep, poll, ask the task for status, or duplicate its work while it runs in background.",
|
|
@@ -508,13 +513,115 @@ export default function (pi) {
|
|
|
508
513
|
isError: true,
|
|
509
514
|
};
|
|
510
515
|
}
|
|
511
|
-
// ── Resolve task identity: new or resume
|
|
516
|
+
// ── Resolve task identity: new, task resume, or conversation resume ──
|
|
517
|
+
const conversationId = normalizeConversationId(params.conversation_id);
|
|
518
|
+
const conversationRegistry = conversationId
|
|
519
|
+
? readConversationRegistry(piDir)
|
|
520
|
+
: {};
|
|
521
|
+
const registeredArtifact = conversationId
|
|
522
|
+
? conversationRegistry[conversationId]
|
|
523
|
+
: undefined;
|
|
524
|
+
const registeredTaskId = registeredArtifact
|
|
525
|
+
? taskIdFromArtifactName(registeredArtifact)
|
|
526
|
+
: undefined;
|
|
527
|
+
if (params.task_id &&
|
|
528
|
+
registeredTaskId &&
|
|
529
|
+
params.task_id !== registeredTaskId) {
|
|
530
|
+
return {
|
|
531
|
+
content: [
|
|
532
|
+
{
|
|
533
|
+
type: "text",
|
|
534
|
+
text: `conversation_id "${conversationId}" maps to ${taskArtifactName(registeredTaskId)}, not ${taskArtifactName(params.task_id)}. Omit task_id or use the mapped task id.`,
|
|
535
|
+
},
|
|
536
|
+
],
|
|
537
|
+
details: {
|
|
538
|
+
phase: "failed",
|
|
539
|
+
error: "conversation_id/task_id mismatch",
|
|
540
|
+
},
|
|
541
|
+
isError: true,
|
|
542
|
+
};
|
|
543
|
+
}
|
|
512
544
|
let id;
|
|
513
545
|
let sessionName;
|
|
514
546
|
let artifactDir;
|
|
515
547
|
let resultPath;
|
|
516
548
|
let resume = false;
|
|
517
|
-
if (
|
|
549
|
+
if (registeredTaskId) {
|
|
550
|
+
id = registeredTaskId;
|
|
551
|
+
sessionName = taskArtifactName(id);
|
|
552
|
+
artifactDir = join(getArtifactsDir(piDir), sessionName);
|
|
553
|
+
resultPath = join(artifactDir, "RESULT.md");
|
|
554
|
+
if (!existsSync(artifactDir)) {
|
|
555
|
+
return {
|
|
556
|
+
content: [
|
|
557
|
+
{
|
|
558
|
+
type: "text",
|
|
559
|
+
text: `conversation_id "${conversationId}" points to missing artifact directory: ${artifactDir}`,
|
|
560
|
+
},
|
|
561
|
+
],
|
|
562
|
+
details: {
|
|
563
|
+
phase: "failed",
|
|
564
|
+
error: "Conversation artifact dir missing",
|
|
565
|
+
conversation_id: conversationId,
|
|
566
|
+
},
|
|
567
|
+
isError: true,
|
|
568
|
+
};
|
|
569
|
+
}
|
|
570
|
+
const metadata = readConversationMetadata(join(artifactDir, "metadata.json"));
|
|
571
|
+
if (metadata?.agent_type && metadata.agent_type !== agent.name) {
|
|
572
|
+
return {
|
|
573
|
+
content: [
|
|
574
|
+
{
|
|
575
|
+
type: "text",
|
|
576
|
+
text: `conversation_id "${conversationId}" belongs to agent "${metadata.agent_type}", not "${agent.name}". Use the original agent_type or start a different conversation_id.`,
|
|
577
|
+
},
|
|
578
|
+
],
|
|
579
|
+
details: {
|
|
580
|
+
phase: "failed",
|
|
581
|
+
error: "conversation_id agent_type mismatch",
|
|
582
|
+
conversation_id: conversationId,
|
|
583
|
+
},
|
|
584
|
+
isError: true,
|
|
585
|
+
};
|
|
586
|
+
}
|
|
587
|
+
resume = true;
|
|
588
|
+
const entry = readRegistry(piDir).find((candidate) => candidate.id === id);
|
|
589
|
+
if (params.background !== false &&
|
|
590
|
+
entry?.paneId &&
|
|
591
|
+
paneExists(entry.paneId)) {
|
|
592
|
+
const bgtask = {
|
|
593
|
+
dir: artifactDir,
|
|
594
|
+
agentType: entry.agentType,
|
|
595
|
+
sessionName,
|
|
596
|
+
paneId: entry.paneId,
|
|
597
|
+
originalPane: null,
|
|
598
|
+
description: params.description || entry.description,
|
|
599
|
+
startedAt: entry.startedAt,
|
|
600
|
+
toolUses: 0,
|
|
601
|
+
turns: 0,
|
|
602
|
+
conversationId,
|
|
603
|
+
recentCalls: [],
|
|
604
|
+
};
|
|
605
|
+
backgroundTasks.set(id, bgtask);
|
|
606
|
+
return {
|
|
607
|
+
content: [
|
|
608
|
+
{
|
|
609
|
+
type: "text",
|
|
610
|
+
text: `Resumed conversation "${conversationId}" via ${taskArtifactName(id)}. The subagent is running in background and will notify on completion.`,
|
|
611
|
+
},
|
|
612
|
+
],
|
|
613
|
+
details: {
|
|
614
|
+
task_id: id,
|
|
615
|
+
agent_type: agent.name,
|
|
616
|
+
description: params.description,
|
|
617
|
+
conversation_id: conversationId,
|
|
618
|
+
tmux_session: sessionName,
|
|
619
|
+
background: true,
|
|
620
|
+
},
|
|
621
|
+
};
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
else if (params.task_id) {
|
|
518
625
|
// Look up the task in the persistent registry
|
|
519
626
|
const entries = readRegistry(piDir);
|
|
520
627
|
const entry = entries.find((e) => e.id === params.task_id);
|
|
@@ -568,6 +675,7 @@ export default function (pi) {
|
|
|
568
675
|
startedAt: entry.startedAt,
|
|
569
676
|
toolUses: 0,
|
|
570
677
|
turns: 0,
|
|
678
|
+
conversationId: entry.conversationId,
|
|
571
679
|
recentCalls: [],
|
|
572
680
|
};
|
|
573
681
|
backgroundTasks.set(id, bgtask);
|
|
@@ -582,6 +690,7 @@ export default function (pi) {
|
|
|
582
690
|
task_id: id,
|
|
583
691
|
agent_type: agent.name,
|
|
584
692
|
description: params.description,
|
|
693
|
+
conversation_id: entry.conversationId ?? conversationId,
|
|
585
694
|
tmux_session: sessionName,
|
|
586
695
|
background: true,
|
|
587
696
|
},
|
|
@@ -590,11 +699,41 @@ export default function (pi) {
|
|
|
590
699
|
}
|
|
591
700
|
else {
|
|
592
701
|
id = `${Date.now().toString(36)}-${randomUUID().slice(0, 4)}`;
|
|
593
|
-
sessionName =
|
|
594
|
-
artifactDir = join(piDir,
|
|
702
|
+
sessionName = taskArtifactName(id);
|
|
703
|
+
artifactDir = join(getArtifactsDir(piDir), sessionName);
|
|
595
704
|
await mkdir(artifactDir, { recursive: true });
|
|
596
705
|
resultPath = join(artifactDir, "RESULT.md");
|
|
597
706
|
}
|
|
707
|
+
if (conversationId && !hasTmux()) {
|
|
708
|
+
return {
|
|
709
|
+
content: [
|
|
710
|
+
{
|
|
711
|
+
type: "text",
|
|
712
|
+
text: "Durable conversations require the tmux/CLI backend so Pi can save and reopen the subagent session. Install/start tmux or omit conversation_id for a one-shot SDK task.",
|
|
713
|
+
},
|
|
714
|
+
],
|
|
715
|
+
details: {
|
|
716
|
+
phase: "failed",
|
|
717
|
+
error: "tmux required for durable conversation",
|
|
718
|
+
conversation_id: conversationId,
|
|
719
|
+
},
|
|
720
|
+
isError: true,
|
|
721
|
+
};
|
|
722
|
+
}
|
|
723
|
+
if (conversationId) {
|
|
724
|
+
await mkdir(artifactDir, { recursive: true });
|
|
725
|
+
conversationRegistry[conversationId] = taskArtifactName(id);
|
|
726
|
+
writeConversationRegistry(piDir, conversationRegistry);
|
|
727
|
+
writeConversationArtifacts({
|
|
728
|
+
taskDir: artifactDir,
|
|
729
|
+
taskId: id,
|
|
730
|
+
conversationId,
|
|
731
|
+
agentType: agent.name,
|
|
732
|
+
sessionDir: join(artifactDir, "sessions"),
|
|
733
|
+
sessionName,
|
|
734
|
+
prompt: params.prompt,
|
|
735
|
+
});
|
|
736
|
+
}
|
|
598
737
|
const descText = params.description || "";
|
|
599
738
|
const isBackground = params.background ?? TASK_BACKGROUND_DEFAULT;
|
|
600
739
|
// default true
|
|
@@ -661,6 +800,7 @@ export default function (pi) {
|
|
|
661
800
|
startedAt: Date.now(),
|
|
662
801
|
toolUses: 0,
|
|
663
802
|
turns: 0,
|
|
803
|
+
conversationId,
|
|
664
804
|
recentCalls: [],
|
|
665
805
|
};
|
|
666
806
|
if (foregroundTask) {
|
|
@@ -679,6 +819,7 @@ export default function (pi) {
|
|
|
679
819
|
startedAt: Date.now(),
|
|
680
820
|
toolUses: 0,
|
|
681
821
|
turns: 0,
|
|
822
|
+
conversationId,
|
|
682
823
|
recentCalls: [],
|
|
683
824
|
};
|
|
684
825
|
backgroundTasks.set(id, bgtask);
|
|
@@ -690,6 +831,7 @@ export default function (pi) {
|
|
|
690
831
|
startedAt: bgtask.startedAt,
|
|
691
832
|
piDir,
|
|
692
833
|
dir: artifactDir,
|
|
834
|
+
conversationId,
|
|
693
835
|
};
|
|
694
836
|
const entries = readRegistry(piDir);
|
|
695
837
|
entries.push(entry);
|
|
@@ -722,6 +864,7 @@ export default function (pi) {
|
|
|
722
864
|
background: true,
|
|
723
865
|
backend: "sdk",
|
|
724
866
|
result_path: resultPath,
|
|
867
|
+
conversation_id: conversationId,
|
|
725
868
|
},
|
|
726
869
|
};
|
|
727
870
|
}
|
|
@@ -736,6 +879,7 @@ export default function (pi) {
|
|
|
736
879
|
backend: "sdk",
|
|
737
880
|
session_path: sessionPath,
|
|
738
881
|
result_path: resultPath,
|
|
882
|
+
conversation_id: conversationId,
|
|
739
883
|
},
|
|
740
884
|
};
|
|
741
885
|
}
|
|
@@ -834,6 +978,7 @@ export default function (pi) {
|
|
|
834
978
|
tool_uses: toolUses,
|
|
835
979
|
turn_count: turns,
|
|
836
980
|
background: false,
|
|
981
|
+
conversation_id: conversationId,
|
|
837
982
|
},
|
|
838
983
|
};
|
|
839
984
|
}
|
|
@@ -848,6 +993,7 @@ export default function (pi) {
|
|
|
848
993
|
startedAt: Date.now(),
|
|
849
994
|
toolUses: 0,
|
|
850
995
|
turns: 0,
|
|
996
|
+
conversationId,
|
|
851
997
|
recentCalls: [],
|
|
852
998
|
};
|
|
853
999
|
backgroundTasks.set(id, bgtask);
|
|
@@ -861,6 +1007,7 @@ export default function (pi) {
|
|
|
861
1007
|
paneId,
|
|
862
1008
|
piDir,
|
|
863
1009
|
dir: artifactDir,
|
|
1010
|
+
conversationId,
|
|
864
1011
|
};
|
|
865
1012
|
// Write to JSON registry for on-load restore
|
|
866
1013
|
const entries = readRegistry(piDir);
|
|
@@ -974,4 +1121,12 @@ export default function (pi) {
|
|
|
974
1121
|
return new Text(line, 0, 0);
|
|
975
1122
|
},
|
|
976
1123
|
});
|
|
1124
|
+
pi.registerCommand("task-sessions", {
|
|
1125
|
+
description: "List durable pi-task conversations",
|
|
1126
|
+
handler: async (_args, ctx) => {
|
|
1127
|
+
const cwd = ctx.sessionManager?.getCwd?.() ?? process.cwd();
|
|
1128
|
+
const { piDir } = discoverAgents(cwd);
|
|
1129
|
+
ctx.ui.notify(renderConversationSessions(piDir), "info");
|
|
1130
|
+
},
|
|
1131
|
+
});
|
|
977
1132
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@heyhuynhgiabuu/pi-task",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"description": "Delegating task/subagent extension for Pi: foreground/background subagents, widgets, tmux observability, SDK fallback.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|