@arhen/pi-core-subagent 1.3.27 → 1.3.28
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/package.json +1 -1
- package/src/format.ts +9 -9
- package/src/graph.ts +1 -1
- package/src/index.ts +2 -2
- package/src/manager.ts +11 -12
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@arhen/pi-core-subagent",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.28",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "pi extension: fast in-process subagents with a dependency-graph scheduler (needs edges gate tasks and carry upstream output into dependent prompts), plus background runs, intercom and agent-to-agent mailbox. Leader defines agents inline.",
|
|
6
6
|
"license": "MIT",
|
package/src/format.ts
CHANGED
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
} from "./types.ts";
|
|
16
16
|
|
|
17
17
|
/** Cap on a single child's final output (and on full-run summaries). */
|
|
18
|
-
|
|
18
|
+
const FINAL_OUTPUT_CAP = 24 * 1024;
|
|
19
19
|
|
|
20
20
|
export function truncateText(text: string, max = FINAL_OUTPUT_CAP): string {
|
|
21
21
|
if (Buffer.byteLength(text, "utf8") <= max) return text;
|
|
@@ -29,7 +29,7 @@ export function getFirstText(message: AssistantMessage): string {
|
|
|
29
29
|
}
|
|
30
30
|
return "";
|
|
31
31
|
}
|
|
32
|
-
|
|
32
|
+
function fmtTokens(n: number): string {
|
|
33
33
|
return n >= 1000 ? `${(n / 1000).toFixed(1).replace(/\.0$/, "")}k` : String(n);
|
|
34
34
|
}
|
|
35
35
|
export function formatUsage(usage: UsageStats): string {
|
|
@@ -48,18 +48,18 @@ export function statusIcon(status: TaskStatus | RunStatus): string {
|
|
|
48
48
|
if (status === "queued") return "○";
|
|
49
49
|
return "•";
|
|
50
50
|
}
|
|
51
|
-
|
|
51
|
+
function fmtDuration(ms: number | undefined): string {
|
|
52
52
|
if (ms === undefined || !Number.isFinite(ms)) return "–";
|
|
53
53
|
const s = Math.max(0, Math.round(ms / 1000));
|
|
54
54
|
return s >= 60 ? `${Math.floor(s / 60)}m${s % 60}s` : `${s}s`;
|
|
55
55
|
}
|
|
56
|
-
|
|
56
|
+
function taskTimer(task: TaskSnapshot): string {
|
|
57
57
|
if (task.startedAt === undefined) return "–";
|
|
58
58
|
const end = task.endedAt ?? Date.now();
|
|
59
59
|
const running = !TERMINAL.includes(task.status);
|
|
60
60
|
return `${running ? "running " : ""}${fmtDuration(end - task.startedAt)}`;
|
|
61
61
|
}
|
|
62
|
-
|
|
62
|
+
function taskStatsWithUsage(task: TaskSnapshot): string {
|
|
63
63
|
const stats = `${task.toolCalls ?? 0} tools`;
|
|
64
64
|
const usage = formatUsage(task.usage);
|
|
65
65
|
return `${stats}${usage ? ` · ${usage}` : ""}`;
|
|
@@ -82,7 +82,7 @@ export function colorNums(text: string, theme: Theme): string {
|
|
|
82
82
|
* Themed one-liner. Finished tasks dim entirely (stats included); live tasks
|
|
83
83
|
* keep the agent name readable with themed numbers.
|
|
84
84
|
*/
|
|
85
|
-
|
|
85
|
+
function themedTaskLine(task: TaskSnapshot, theme: Theme, activity = ""): string {
|
|
86
86
|
const tail = `${taskStatsWithUsage(task)} · ${taskTimer(task)}`;
|
|
87
87
|
// Queued task with unmet needs: show the gate it's waiting on instead of empty stats.
|
|
88
88
|
const gate =
|
|
@@ -101,7 +101,7 @@ export function themedTaskLine(task: TaskSnapshot, theme: Theme, activity = ""):
|
|
|
101
101
|
* unknown/custom tools then read fine too. Add a case only if one reads badly.
|
|
102
102
|
*/
|
|
103
103
|
// Order matters: the most specific arg wins (grep's pattern beats its path).
|
|
104
|
-
|
|
104
|
+
const ARG_KEYS = [
|
|
105
105
|
"pattern",
|
|
106
106
|
"query",
|
|
107
107
|
"command",
|
|
@@ -132,7 +132,7 @@ export function activitySnippet(text: string): string {
|
|
|
132
132
|
}
|
|
133
133
|
|
|
134
134
|
/** Mailbox/intercom tools — while one is the task's last activity, the agent is "talking". */
|
|
135
|
-
|
|
135
|
+
const TALK_TOOLS = ["poll_agent_messages", "send_agent_message", "ask_parent", "notify_parent"];
|
|
136
136
|
export function isTalking(task: TaskSnapshot): boolean {
|
|
137
137
|
const a = task.lastActivity?.toLowerCase() ?? "";
|
|
138
138
|
return TALK_TOOLS.some((t) => a.startsWith(t));
|
|
@@ -155,7 +155,7 @@ export function compactLines(run: RunSnapshot): string[] {
|
|
|
155
155
|
* └─ ✓ reviewer · 6 tools · 44s
|
|
156
156
|
* Static icons (no animation); latest activity + tool count + runtime per agent.
|
|
157
157
|
*/
|
|
158
|
-
|
|
158
|
+
const WIDGET_MAX_LINES = 10;
|
|
159
159
|
|
|
160
160
|
export class SubagentsWidget implements Component {
|
|
161
161
|
constructor(
|
package/src/graph.ts
CHANGED
|
@@ -85,7 +85,7 @@ export function applyUpstream(task: string, needs: string[], outputs: Map<string
|
|
|
85
85
|
return `${blocks.join("\n\n")}\n\n---\n\n${body}`;
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
-
|
|
88
|
+
async function mapWithConcurrency<T>(
|
|
89
89
|
items: T[],
|
|
90
90
|
concurrency: number,
|
|
91
91
|
fn: (item: T, index: number) => Promise<void>,
|
package/src/index.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* pi-core-subagent — in-process subagents.
|
|
3
3
|
*
|
|
4
4
|
* Fast in-process subagents (isolated AgentSessions, no process spawn).
|
|
5
5
|
* Modes: single / parallel / chain. Background runs, cancel, intercom
|
|
6
6
|
* (ask/notify/update the leader) and agent↔agent mailbox (send/poll).
|
|
7
7
|
*
|
|
8
|
-
* Context discipline:
|
|
8
|
+
* Context discipline: 7 slim parent tools, one-line catalog injected per
|
|
9
9
|
* request (cached), background completions notify with a 3-line summary
|
|
10
10
|
* instead of full outputs, and run updates are throttled (no per-event
|
|
11
11
|
* deep clones).
|
package/src/manager.ts
CHANGED
|
@@ -47,21 +47,21 @@ import {
|
|
|
47
47
|
export const DEFAULT_CONCURRENCY = 3;
|
|
48
48
|
export const MAX_CONCURRENCY = 8;
|
|
49
49
|
/** No default wall-clock cap: a subagent runs until its task is done, it stalls, or the user aborts. */
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
50
|
+
const DEFAULT_RUNTIME_MS = 0;
|
|
51
|
+
const DEFAULT_STALL_MS = 180_000; // 3 min: long model thinking streams emit no events, but they're not stalled.
|
|
52
|
+
const READONLY_TOOLS = ["read", "grep", "find", "ls"];
|
|
53
|
+
const WRITE_TOOLS = ["read", "grep", "find", "ls", "bash", "edit", "write"];
|
|
54
54
|
const WIDGET_THROTTLE_MS = 150;
|
|
55
55
|
|
|
56
56
|
// ── helpers ──────────────────────────────────────────────────────────────
|
|
57
57
|
|
|
58
|
-
|
|
58
|
+
function newId(prefix: string): string {
|
|
59
59
|
return `${prefix}_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
|
|
60
60
|
}
|
|
61
|
-
|
|
61
|
+
function emptyUsage(): UsageStats {
|
|
62
62
|
return { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0, turns: 0 };
|
|
63
63
|
}
|
|
64
|
-
|
|
64
|
+
function aggregateUsage(tasks: TaskSnapshot[]): UsageStats {
|
|
65
65
|
const total = emptyUsage();
|
|
66
66
|
for (const task of tasks) {
|
|
67
67
|
total.input += task.usage.input;
|
|
@@ -73,7 +73,7 @@ export function aggregateUsage(tasks: TaskSnapshot[]): UsageStats {
|
|
|
73
73
|
}
|
|
74
74
|
return total;
|
|
75
75
|
}
|
|
76
|
-
|
|
76
|
+
function getParentSessionFile(ctx: ExtensionContext): string | undefined {
|
|
77
77
|
try {
|
|
78
78
|
return ctx.sessionManager.getSessionFile?.();
|
|
79
79
|
} catch {
|
|
@@ -92,7 +92,7 @@ export function classifyFailure(
|
|
|
92
92
|
if (stopReason === "aborted") return { status: "aborted", message: errorMessage || "Subagent was aborted." };
|
|
93
93
|
return { status: "failed", message: errorMessage || `Subagent ended with stopReason "${stopReason}".` };
|
|
94
94
|
}
|
|
95
|
-
|
|
95
|
+
function lastAssistantFailure(
|
|
96
96
|
messages: AssistantMessage[] | undefined,
|
|
97
97
|
): { status: "failed" | "aborted"; message: string } | undefined {
|
|
98
98
|
for (const message of [...(messages ?? [])].reverse()) {
|
|
@@ -101,12 +101,12 @@ export function lastAssistantFailure(
|
|
|
101
101
|
}
|
|
102
102
|
return undefined;
|
|
103
103
|
}
|
|
104
|
-
|
|
104
|
+
function failureError(failure: { status: "failed" | "aborted"; message: string }): Error {
|
|
105
105
|
const error = new Error(failure.message);
|
|
106
106
|
(error as Error & { subagentStatus?: string }).subagentStatus = failure.status;
|
|
107
107
|
return error;
|
|
108
108
|
}
|
|
109
|
-
|
|
109
|
+
function updateUsageFromMessage(task: TaskSnapshot, message: AssistantMessage): void {
|
|
110
110
|
if (message?.role !== "assistant") return;
|
|
111
111
|
task.usage.turns += 1;
|
|
112
112
|
const usage = message.usage;
|
|
@@ -1080,7 +1080,6 @@ export class SubagentManager {
|
|
|
1080
1080
|
s(cloneRun(run));
|
|
1081
1081
|
}
|
|
1082
1082
|
|
|
1083
|
-
/** Child→leader messages collected while the parent is parked in await_subagent. */
|
|
1084
1083
|
/** Child→leader messages collected while the parent is parked in await_subagent. */
|
|
1085
1084
|
private parked = new Map<string, { msgs: ParkedMsg[]; wake: () => void }>();
|
|
1086
1085
|
|