@heyhuynhgiabuu/pi-task 0.1.6 → 0.2.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/CHANGELOG.md +67 -3
- package/dist/constants.d.ts +4 -0
- package/dist/constants.js +4 -0
- package/dist/conversation.d.ts +8 -0
- package/dist/conversation.js +96 -1
- package/dist/helpers.d.ts +2 -2
- package/dist/helpers.js +4 -9
- package/dist/index.d.ts +6 -23
- package/dist/index.js +91 -589
- package/dist/lifecycle/completion.d.ts +3 -0
- package/dist/lifecycle/completion.js +50 -0
- package/dist/lifecycle/index.d.ts +5 -0
- package/dist/lifecycle/index.js +5 -0
- package/dist/lifecycle/polling.d.ts +16 -0
- package/dist/lifecycle/polling.js +61 -0
- package/dist/lifecycle/restore.d.ts +2 -0
- package/dist/lifecycle/restore.js +34 -0
- package/dist/lifecycle/toolStats.d.ts +2 -0
- package/dist/lifecycle/toolStats.js +17 -0
- package/dist/lifecycle/widget.d.ts +8 -0
- package/dist/lifecycle/widget.js +75 -0
- package/dist/session-text.d.ts +9 -0
- package/dist/session-text.js +50 -0
- package/dist/subagent/runSdk.js +50 -26
- package/dist/subagent/tmux.d.ts +12 -9
- package/dist/subagent/tmux.js +107 -44
- package/dist/subagent/waitCompletion.d.ts +4 -5
- package/dist/subagent/waitCompletion.js +27 -43
- package/dist/tool/index.d.ts +5 -0
- package/dist/tool/index.js +5 -0
- package/dist/tool/prompt.d.ts +8 -0
- package/dist/tool/prompt.js +17 -0
- package/dist/tool/renderCall.d.ts +3 -0
- package/dist/tool/renderCall.js +12 -0
- package/dist/tool/renderResult.d.ts +8 -0
- package/dist/tool/renderResult.js +51 -0
- package/dist/tool/schema.d.ts +8 -0
- package/dist/tool/schema.js +24 -0
- package/dist/tool/taskComplete.d.ts +8 -0
- package/dist/tool/taskComplete.js +65 -0
- package/dist/types.d.ts +54 -0
- package/dist/types.js +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import type { BackgroundTask } from "../types.js";
|
|
3
|
+
export declare function completeTask(pi: ExtensionAPI, id: string, task: BackgroundTask, content: string, phase: "done" | "timeout" | "failed", piDir: string): void;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { findJsonlSessionByName, readRegistry, upsertTaskSessionHistory, writeRegistry } from "../conversation.js";
|
|
2
|
+
import { parseResultXml } from "../helpers.js";
|
|
3
|
+
import { killAgentPane } from "../subagent/tmux.js";
|
|
4
|
+
export function completeTask(pi, id, task, content, phase, piDir) {
|
|
5
|
+
// Kill the tmux pane if still alive.
|
|
6
|
+
if (task.paneId)
|
|
7
|
+
killAgentPane(task.paneId, task.originalPane);
|
|
8
|
+
const parsed = parseResultXml(content);
|
|
9
|
+
const durationMs = Date.now() - task.startedAt;
|
|
10
|
+
const completedSessionRef = findJsonlSessionByName(piDir, task.sessionName, task.agentType)?.sessionRef;
|
|
11
|
+
upsertTaskSessionHistory(piDir, {
|
|
12
|
+
id,
|
|
13
|
+
agentType: task.agentType,
|
|
14
|
+
description: task.description,
|
|
15
|
+
sessionName: task.sessionName,
|
|
16
|
+
startedAt: task.startedAt,
|
|
17
|
+
paneId: task.paneId,
|
|
18
|
+
piDir,
|
|
19
|
+
dir: task.dir,
|
|
20
|
+
conversationId: task.conversationId,
|
|
21
|
+
sessionRef: completedSessionRef,
|
|
22
|
+
status: phase,
|
|
23
|
+
completedAt: Date.now(),
|
|
24
|
+
background: true,
|
|
25
|
+
});
|
|
26
|
+
pi.sendMessage({
|
|
27
|
+
customType: "task-complete",
|
|
28
|
+
content: `Background task ${id} (${task.agentType}) ${phase}.\n\nResult:\n${content}`,
|
|
29
|
+
display: true,
|
|
30
|
+
details: {
|
|
31
|
+
task_id: id,
|
|
32
|
+
agent_type: task.agentType,
|
|
33
|
+
description: task.description,
|
|
34
|
+
phase,
|
|
35
|
+
status: phase,
|
|
36
|
+
result: content,
|
|
37
|
+
summary: parsed.summary,
|
|
38
|
+
findings: parsed.findings,
|
|
39
|
+
confidence: parsed.confidence,
|
|
40
|
+
duration_ms: durationMs,
|
|
41
|
+
tool_uses: task.toolUses,
|
|
42
|
+
turn_count: task.turns,
|
|
43
|
+
},
|
|
44
|
+
}, {
|
|
45
|
+
triggerTurn: true,
|
|
46
|
+
deliverAs: "followUp",
|
|
47
|
+
});
|
|
48
|
+
const entries = readRegistry(piDir).filter((entry) => entry.id !== id);
|
|
49
|
+
writeRegistry(piDir, entries);
|
|
50
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { completeTask } from "./completion.js";
|
|
2
|
+
export { startBackgroundPolling } from "./polling.js";
|
|
3
|
+
export { restoreActiveBackgroundTasks } from "./restore.js";
|
|
4
|
+
export { startToolStatsPolling } from "./toolStats.js";
|
|
5
|
+
export { createTaskWidgetController } from "./widget.js";
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { completeTask } from "./completion.js";
|
|
2
|
+
export { startBackgroundPolling } from "./polling.js";
|
|
3
|
+
export { restoreActiveBackgroundTasks } from "./restore.js";
|
|
4
|
+
export { startToolStatsPolling } from "./toolStats.js";
|
|
5
|
+
export { createTaskWidgetController } from "./widget.js";
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Background polling logic for task completion.
|
|
3
|
+
* This module encapsulates the interval-based checking of background tasks.
|
|
4
|
+
*/
|
|
5
|
+
export interface PollingDeps {
|
|
6
|
+
backgroundTasks: Map<string, any>;
|
|
7
|
+
checkTaskCompletion: (opts: any) => Promise<any>;
|
|
8
|
+
killAgentPane: (paneId: string | undefined, originalPane: string | null) => void;
|
|
9
|
+
clearTaskWidgetIfIdle: () => void;
|
|
10
|
+
completeTask: (pi: any, id: string, task: any, content: string, phase: "done" | "timeout" | "failed", piDir: string) => void;
|
|
11
|
+
TASK_TIMEOUT_MS: number;
|
|
12
|
+
MAX_POLL_ERRORS: number;
|
|
13
|
+
piDir: string;
|
|
14
|
+
pi: any;
|
|
15
|
+
}
|
|
16
|
+
export declare function startBackgroundPolling(deps: PollingDeps, intervalMs: number): NodeJS.Timeout;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
let checkInFlight = false;
|
|
3
|
+
export function startBackgroundPolling(deps, intervalMs) {
|
|
4
|
+
const { backgroundTasks, checkTaskCompletion, killAgentPane, clearTaskWidgetIfIdle, completeTask, TASK_TIMEOUT_MS, MAX_POLL_ERRORS, piDir, pi, } = deps;
|
|
5
|
+
return setInterval(async () => {
|
|
6
|
+
if (checkInFlight)
|
|
7
|
+
return;
|
|
8
|
+
if (backgroundTasks.size === 0) {
|
|
9
|
+
clearTaskWidgetIfIdle();
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
checkInFlight = true;
|
|
13
|
+
try {
|
|
14
|
+
const now = Date.now();
|
|
15
|
+
const ids = Array.from(backgroundTasks.keys());
|
|
16
|
+
for (const id of ids) {
|
|
17
|
+
const task = backgroundTasks.get(id);
|
|
18
|
+
if (!task)
|
|
19
|
+
continue;
|
|
20
|
+
if (now - task.startedAt > TASK_TIMEOUT_MS) {
|
|
21
|
+
killAgentPane(task.paneId, task.originalPane);
|
|
22
|
+
backgroundTasks.delete(id);
|
|
23
|
+
clearTaskWidgetIfIdle();
|
|
24
|
+
completeTask(pi, id, task, "Task timed out after 30 minutes", "timeout", piDir);
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
let snapshot;
|
|
28
|
+
try {
|
|
29
|
+
snapshot = await checkTaskCompletion({
|
|
30
|
+
sessionDir: join(task.dir, "sessions", id),
|
|
31
|
+
sessionName: task.sessionName,
|
|
32
|
+
paneId: task.paneId,
|
|
33
|
+
sinceMs: task.startedAt,
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
catch (err) {
|
|
37
|
+
task.pollErrors = (task.pollErrors ?? 0) + 1;
|
|
38
|
+
if (task.pollErrors >= MAX_POLL_ERRORS) {
|
|
39
|
+
killAgentPane(task.paneId, task.originalPane);
|
|
40
|
+
backgroundTasks.delete(id);
|
|
41
|
+
clearTaskWidgetIfIdle();
|
|
42
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
43
|
+
completeTask(pi, id, task, `Task ${id} polling failed ${task.pollErrors}x; last error: ${message}`, "failed", piDir);
|
|
44
|
+
}
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
task.pollErrors = 0;
|
|
48
|
+
if (snapshot.status === "running") {
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
const phase = snapshot.status === "completed" ? "done" : "failed";
|
|
52
|
+
backgroundTasks.delete(id);
|
|
53
|
+
clearTaskWidgetIfIdle();
|
|
54
|
+
completeTask(pi, id, task, snapshot.content, phase, piDir);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
finally {
|
|
58
|
+
checkInFlight = false;
|
|
59
|
+
}
|
|
60
|
+
}, intervalMs);
|
|
61
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { readRegistry, writeRegistry } from "../conversation.js";
|
|
3
|
+
import { paneExists } from "../subagent/tmux.js";
|
|
4
|
+
export function restoreActiveBackgroundTasks(piDir, backgroundTasks) {
|
|
5
|
+
const registry = readRegistry(piDir);
|
|
6
|
+
const staleIds = [];
|
|
7
|
+
for (const entry of registry) {
|
|
8
|
+
if (!existsSync(entry.dir)) {
|
|
9
|
+
staleIds.push(entry.id);
|
|
10
|
+
continue;
|
|
11
|
+
}
|
|
12
|
+
const paneAlive = entry.paneId ? paneExists(entry.paneId) : false;
|
|
13
|
+
if (!paneAlive) {
|
|
14
|
+
staleIds.push(entry.id);
|
|
15
|
+
continue;
|
|
16
|
+
}
|
|
17
|
+
backgroundTasks.set(entry.id, {
|
|
18
|
+
dir: entry.dir,
|
|
19
|
+
agentType: entry.agentType,
|
|
20
|
+
sessionName: entry.sessionName,
|
|
21
|
+
paneId: entry.paneId,
|
|
22
|
+
originalPane: null,
|
|
23
|
+
description: entry.description,
|
|
24
|
+
startedAt: entry.startedAt,
|
|
25
|
+
toolUses: 0,
|
|
26
|
+
turns: 0,
|
|
27
|
+
conversationId: entry.conversationId,
|
|
28
|
+
recentCalls: [],
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
if (staleIds.length) {
|
|
32
|
+
writeRegistry(piDir, registry.filter((entry) => !staleIds.includes(entry.id)));
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
import { readRecentToolCalls } from "../helpers.js";
|
|
3
|
+
export function startToolStatsPolling(foregroundTasks, backgroundTasks, intervalMs) {
|
|
4
|
+
return setInterval(() => {
|
|
5
|
+
const trackedTasks = [
|
|
6
|
+
...foregroundTasks.entries(),
|
|
7
|
+
...backgroundTasks.entries(),
|
|
8
|
+
];
|
|
9
|
+
for (const [id, task] of trackedTasks) {
|
|
10
|
+
const sessionDir = join(task.dir, "sessions", id);
|
|
11
|
+
const { toolUses, turns, recent } = readRecentToolCalls(sessionDir, 12, task.sessionName);
|
|
12
|
+
task.toolUses = toolUses;
|
|
13
|
+
task.turns = turns;
|
|
14
|
+
task.recentCalls = recent;
|
|
15
|
+
}
|
|
16
|
+
}, intervalMs);
|
|
17
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import type { BackgroundTask } from "../types.js";
|
|
3
|
+
export interface TaskWidgetController {
|
|
4
|
+
ensureTaskWidget(targetCtx: ExtensionContext): void;
|
|
5
|
+
clearTaskWidgetIfIdle(): void;
|
|
6
|
+
dispose(): void;
|
|
7
|
+
}
|
|
8
|
+
export declare function createTaskWidgetController(foregroundTasks: Map<string, BackgroundTask>, backgroundTasks: Map<string, BackgroundTask>): TaskWidgetController;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { truncateToWidth } from "@earendil-works/pi-tui";
|
|
2
|
+
import { formatMs } from "../helpers.js";
|
|
3
|
+
import { TASK_WIDGET_RENDER_MS, renderTaskWidget, } from "../task-widget.js";
|
|
4
|
+
export function createTaskWidgetController(foregroundTasks, backgroundTasks) {
|
|
5
|
+
let widgetCtx = null;
|
|
6
|
+
let widgetTimer = null;
|
|
7
|
+
let widgetTheme = null;
|
|
8
|
+
function stopWidget() {
|
|
9
|
+
if (widgetTimer) {
|
|
10
|
+
clearInterval(widgetTimer);
|
|
11
|
+
widgetTimer = null;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
function renderWidget(width) {
|
|
15
|
+
try {
|
|
16
|
+
return renderTaskWidget({
|
|
17
|
+
foregroundTasks: foregroundTasks.entries(),
|
|
18
|
+
backgroundTasks: backgroundTasks.entries(),
|
|
19
|
+
foregroundCount: foregroundTasks.size,
|
|
20
|
+
backgroundCount: backgroundTasks.size,
|
|
21
|
+
width,
|
|
22
|
+
theme: widgetTheme,
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
catch (err) {
|
|
26
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
27
|
+
const active = [
|
|
28
|
+
...Array.from(foregroundTasks.entries()),
|
|
29
|
+
...Array.from(backgroundTasks.entries()),
|
|
30
|
+
];
|
|
31
|
+
if (active.length === 0)
|
|
32
|
+
return [];
|
|
33
|
+
const [, task] = active[0];
|
|
34
|
+
return [
|
|
35
|
+
truncateToWidth(`${task.agentType} • ${formatMs(Date.now() - task.startedAt)} (render error: ${msg})`, Math.min(width, 120)),
|
|
36
|
+
];
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
function ensureTaskWidget(targetCtx) {
|
|
40
|
+
if (widgetCtx || targetCtx.mode !== "tui")
|
|
41
|
+
return;
|
|
42
|
+
widgetCtx = targetCtx;
|
|
43
|
+
targetCtx.ui.setWidget("task", (tui, theme) => {
|
|
44
|
+
widgetTheme = theme ?? null;
|
|
45
|
+
widgetTimer = setInterval(() => tui.requestRender(), TASK_WIDGET_RENDER_MS);
|
|
46
|
+
widgetTimer.unref?.();
|
|
47
|
+
return {
|
|
48
|
+
render: (width) => renderWidget(width),
|
|
49
|
+
invalidate: () => { },
|
|
50
|
+
dispose: () => {
|
|
51
|
+
widgetTheme = null;
|
|
52
|
+
stopWidget();
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
function clearTaskWidgetIfIdle() {
|
|
58
|
+
if (foregroundTasks.size > 0 || backgroundTasks.size > 0)
|
|
59
|
+
return;
|
|
60
|
+
if (widgetCtx) {
|
|
61
|
+
widgetCtx.ui.setWidget("task", undefined);
|
|
62
|
+
widgetCtx = null;
|
|
63
|
+
}
|
|
64
|
+
stopWidget();
|
|
65
|
+
}
|
|
66
|
+
function dispose() {
|
|
67
|
+
if (widgetCtx) {
|
|
68
|
+
widgetCtx.ui.setWidget("task", undefined);
|
|
69
|
+
widgetCtx = null;
|
|
70
|
+
}
|
|
71
|
+
widgetTheme = null;
|
|
72
|
+
stopWidget();
|
|
73
|
+
}
|
|
74
|
+
return { ensureTaskWidget, clearTaskWidgetIfIdle, dispose };
|
|
75
|
+
}
|
package/dist/session-text.d.ts
CHANGED
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Read assistant text from pi JSONL session directories (task / harness).
|
|
3
3
|
*/
|
|
4
|
+
/**
|
|
5
|
+
* Whether the subagent has finished producing responses.
|
|
6
|
+
*
|
|
7
|
+
* The reliable completion signal is the last assistant message's stopReason.
|
|
8
|
+
* - "toolUse": the agent called tools and should continue, so it is not done.
|
|
9
|
+
* - "stop" / "endTurn" / "length" / "error" / "aborted": terminal.
|
|
10
|
+
* - no assistant messages or no stopReason yet: not done.
|
|
11
|
+
*/
|
|
12
|
+
export declare function hasAgentFinished(sessionDir: string, sessionName?: string, sinceMs?: number): boolean;
|
|
4
13
|
/**
|
|
5
14
|
* Last non-empty assistant message from matching .jsonl files in sessionDir.
|
|
6
15
|
*/
|
package/dist/session-text.js
CHANGED
|
@@ -33,6 +33,56 @@ function matchesSessionName(content, sessionName) {
|
|
|
33
33
|
}
|
|
34
34
|
return false;
|
|
35
35
|
}
|
|
36
|
+
/**
|
|
37
|
+
* Whether the subagent has finished producing responses.
|
|
38
|
+
*
|
|
39
|
+
* The reliable completion signal is the last assistant message's stopReason.
|
|
40
|
+
* - "toolUse": the agent called tools and should continue, so it is not done.
|
|
41
|
+
* - "stop" / "endTurn" / "length" / "error" / "aborted": terminal.
|
|
42
|
+
* - no assistant messages or no stopReason yet: not done.
|
|
43
|
+
*/
|
|
44
|
+
export function hasAgentFinished(sessionDir, sessionName, sinceMs) {
|
|
45
|
+
if (!existsSync(sessionDir))
|
|
46
|
+
return false;
|
|
47
|
+
const files = readdirSync(sessionDir)
|
|
48
|
+
.filter((f) => f.endsWith(".jsonl"))
|
|
49
|
+
.sort();
|
|
50
|
+
let lastStopReason;
|
|
51
|
+
for (const file of files) {
|
|
52
|
+
const content = readFileSync(join(sessionDir, file), "utf-8");
|
|
53
|
+
if (!matchesSessionName(content, sessionName))
|
|
54
|
+
continue;
|
|
55
|
+
for (const rawLine of content.split("\n")) {
|
|
56
|
+
const line = rawLine.trim();
|
|
57
|
+
if (!line)
|
|
58
|
+
continue;
|
|
59
|
+
try {
|
|
60
|
+
const entry = JSON.parse(line);
|
|
61
|
+
if (entry.type !== "message")
|
|
62
|
+
continue;
|
|
63
|
+
if (sinceMs !== undefined && entry.timestamp) {
|
|
64
|
+
const timestampMs = Date.parse(entry.timestamp);
|
|
65
|
+
if (Number.isFinite(timestampMs) && timestampMs < sinceMs)
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
const msg = entry.message;
|
|
69
|
+
if (!msg || msg.role !== "assistant")
|
|
70
|
+
continue;
|
|
71
|
+
if (typeof msg.stopReason === "string") {
|
|
72
|
+
lastStopReason = msg.stopReason;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
/* skip malformed JSONL rows */
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
if (!lastStopReason)
|
|
81
|
+
return false;
|
|
82
|
+
if (lastStopReason === "toolUse")
|
|
83
|
+
return false;
|
|
84
|
+
return ["stop", "endTurn", "length", "error", "aborted"].includes(lastStopReason);
|
|
85
|
+
}
|
|
36
86
|
/**
|
|
37
87
|
* Last non-empty assistant message from matching .jsonl files in sessionDir.
|
|
38
88
|
*/
|
package/dist/subagent/runSdk.js
CHANGED
|
@@ -1,42 +1,66 @@
|
|
|
1
|
-
function resolveModel(ctx, requested) {
|
|
1
|
+
async function resolveModel(ctx, requested) {
|
|
2
2
|
const registry = ctx.modelRegistry;
|
|
3
|
-
const available = registry?.getAll?.() ?? registry?.getAvailable?.() ?? [];
|
|
4
3
|
if (requested) {
|
|
5
4
|
const [provider, ...rest] = requested.split("/");
|
|
6
5
|
const modelId = rest.join("/");
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
if (
|
|
11
|
-
return
|
|
12
|
-
|
|
6
|
+
const exact = modelId
|
|
7
|
+
? registry?.find?.(provider, modelId)
|
|
8
|
+
: registry?.find?.(requested);
|
|
9
|
+
if (exact)
|
|
10
|
+
return exact;
|
|
11
|
+
}
|
|
12
|
+
const all = registry?.getAll?.() ?? [];
|
|
13
|
+
const available = all.length > 0 ? all : ((await registry?.getAvailable?.()) ?? []);
|
|
14
|
+
if (requested) {
|
|
15
|
+
const byId = available.find((model) => model?.id === requested ||
|
|
16
|
+
`${model?.provider?.id ?? model?.provider}/${model?.id}` === requested ||
|
|
17
|
+
model?.name === requested);
|
|
13
18
|
if (byId)
|
|
14
19
|
return byId;
|
|
15
20
|
}
|
|
16
21
|
return available[0];
|
|
17
22
|
}
|
|
18
23
|
export async function runSdkSubagent(options) {
|
|
19
|
-
const model = resolveModel(options.ctx, options.model ?? options.agent.model);
|
|
24
|
+
const model = await resolveModel(options.ctx, options.model ?? options.agent.model);
|
|
20
25
|
if (!model) {
|
|
21
26
|
throw new Error("No model available for SDK subagent execution");
|
|
22
27
|
}
|
|
23
|
-
const { createAgentSession, DefaultResourceLoader } = await import("@earendil-works/pi-coding-agent");
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
28
|
+
const { createAgentSession, DefaultResourceLoader, getAgentDir } = await import("@earendil-works/pi-coding-agent");
|
|
29
|
+
const previousDisabled = process.env.PI_TASK_TOOL_DISABLED;
|
|
30
|
+
process.env.PI_TASK_TOOL_DISABLED = "1";
|
|
31
|
+
let session;
|
|
32
|
+
try {
|
|
33
|
+
const agentDir = getAgentDir();
|
|
34
|
+
const resourceLoader = new DefaultResourceLoader({
|
|
35
|
+
cwd: options.cwd,
|
|
36
|
+
agentDir,
|
|
37
|
+
systemPromptOverride: () => options.systemPrompt,
|
|
38
|
+
noExtensions: true,
|
|
39
|
+
});
|
|
40
|
+
await resourceLoader.reload();
|
|
41
|
+
({ session } = await createAgentSession({
|
|
42
|
+
cwd: options.cwd,
|
|
43
|
+
agentDir,
|
|
44
|
+
model,
|
|
45
|
+
thinkingLevel: options.thinkingLevel,
|
|
46
|
+
tools: options.tools,
|
|
47
|
+
excludeTools: options.excludeTools,
|
|
48
|
+
resourceLoader,
|
|
49
|
+
}));
|
|
50
|
+
await session.prompt(options.prompt);
|
|
51
|
+
const sessionPath = session.sessionFile;
|
|
52
|
+
const output = getLastAssistantText(session.messages);
|
|
53
|
+
return { output: output.trim(), sessionPath };
|
|
54
|
+
}
|
|
55
|
+
finally {
|
|
56
|
+
session?.dispose?.();
|
|
57
|
+
if (previousDisabled === undefined) {
|
|
58
|
+
delete process.env.PI_TASK_TOOL_DISABLED;
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
process.env.PI_TASK_TOOL_DISABLED = previousDisabled;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
40
64
|
}
|
|
41
65
|
function getLastAssistantText(messages) {
|
|
42
66
|
for (let index = messages.length - 1; index >= 0; index -= 1) {
|
package/dist/subagent/tmux.d.ts
CHANGED
|
@@ -1,18 +1,21 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
export type TmuxSplitResult = {
|
|
2
|
+
paneId: string;
|
|
3
|
+
originalPane: string | null;
|
|
4
|
+
};
|
|
4
5
|
export declare function tmuxCmd(args: string[]): string;
|
|
5
6
|
export declare function hasTmux(): boolean;
|
|
6
|
-
export declare function paneExists(paneId: string): boolean;
|
|
7
7
|
export declare function getCurrentPaneId(): string | null;
|
|
8
8
|
export declare function getCurrentPaneSize(targetPane?: string | null): {
|
|
9
9
|
width: number;
|
|
10
10
|
height: number;
|
|
11
11
|
} | null;
|
|
12
|
-
export declare function splitWindowPane(cwd: string, command: string):
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
export declare function
|
|
12
|
+
export declare function splitWindowPane(cwd: string, command: string): TmuxSplitResult;
|
|
13
|
+
export declare function setPaneRemainOnExit(paneId: string, enabled: boolean): void;
|
|
14
|
+
export declare function setPaneSelfDestruct(paneId: string, enabled: boolean, delaySeconds?: number): void;
|
|
15
|
+
export declare function paneExists(paneId: string): boolean;
|
|
16
|
+
export declare function paneDead(paneId: string): boolean;
|
|
17
|
+
export declare function capturePaneTail(paneId: string, lines?: number): string;
|
|
18
|
+
export declare function killAgentPane(paneId: string, originalPane?: string | null): void;
|
|
17
19
|
/** Inject text into a running subagent pane (steer / follow-up). */
|
|
18
20
|
export declare function tmuxSteerPane(paneId: string, message: string): void;
|
|
21
|
+
export declare function wrapWithPaneExitWatcher(sessionFilePath: string, command: string): string;
|