@arhen/pi-core-subagent 1.3.2 → 1.3.4
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 +2 -1
- package/package.json +54 -46
- package/src/child.ts +17 -15
- package/src/format.ts +237 -0
- package/src/graph.ts +145 -0
- package/src/index.ts +94 -1315
- package/src/manager.ts +1051 -0
- package/src/peek.ts +4 -2
- package/src/schemas.ts +88 -0
- package/src/types.ts +70 -0
package/src/peek.ts
CHANGED
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import { closeSync, openSync, readSync, statSync } from "node:fs";
|
|
10
|
-
import { Key, matchesKey, truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
|
|
11
10
|
import type { Theme } from "@earendil-works/pi-coding-agent";
|
|
11
|
+
import { Key, matchesKey, truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
|
|
12
12
|
|
|
13
13
|
/** Tail window: last 64KB of the child session file is plenty for a peek. */
|
|
14
14
|
const TAIL_BYTES = 64 * 1024;
|
|
@@ -56,7 +56,9 @@ function eventLine(raw: string): [string, string] | null {
|
|
|
56
56
|
const out: [string, string][] = [];
|
|
57
57
|
for (const block of msg.content ?? []) {
|
|
58
58
|
if (block.type === "toolCall") {
|
|
59
|
-
const arg = Object.values(block.arguments ?? {}).find((v) => typeof v === "string" && v.trim() !== "") as
|
|
59
|
+
const arg = Object.values(block.arguments ?? {}).find((v) => typeof v === "string" && v.trim() !== "") as
|
|
60
|
+
| string
|
|
61
|
+
| undefined;
|
|
60
62
|
out.push(["→", `${block.name}${arg ? ` ${clip(arg, 120)}` : ""}`]);
|
|
61
63
|
} else if (block.type === "text" && block.text?.trim()) {
|
|
62
64
|
out.push([msg.role === "toolResult" ? "←" : "·", clip(block.text, 160)]);
|
package/src/schemas.ts
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/** Tool schemas — single source of truth. TaskInput/SubagentParamsShape are
|
|
2
|
+
* derived from them, so the shapes can never drift from what the model sees. */
|
|
3
|
+
import { StringEnum } from "@earendil-works/pi-ai";
|
|
4
|
+
import { type Static, Type } from "typebox";
|
|
5
|
+
import { DEFAULT_CONCURRENCY, MAX_CONCURRENCY } from "./manager.ts";
|
|
6
|
+
|
|
7
|
+
const THINKING_LEVELS = ["off", "minimal", "low", "medium", "high", "xhigh", "max"] as const;
|
|
8
|
+
const TaskItem = Type.Object({
|
|
9
|
+
id: Type.Optional(Type.String({ description: "Optional stable task id" })),
|
|
10
|
+
agent: Type.String({
|
|
11
|
+
minLength: 1,
|
|
12
|
+
description:
|
|
13
|
+
"Agent name you invent. Always define the agent inline: prompt (system prompt) + toolset (write: true for write access). Never create agent files.",
|
|
14
|
+
}),
|
|
15
|
+
task: Type.String({ minLength: 1, description: "Task for this agent" }),
|
|
16
|
+
prompt: Type.Optional(
|
|
17
|
+
Type.String({ description: "System prompt defining this agent's behavior. Optional — a minimal default is used." }),
|
|
18
|
+
),
|
|
19
|
+
write: Type.Optional(
|
|
20
|
+
Type.Boolean({
|
|
21
|
+
description: "true = write toolset (read, bash, edit, write); default false = read-only (read, grep, find, ls)",
|
|
22
|
+
}),
|
|
23
|
+
),
|
|
24
|
+
model: Type.Optional(Type.String({ description: "Model override (provider/model-id)" })),
|
|
25
|
+
thinking: Type.Optional(StringEnum(THINKING_LEVELS, { description: "Thinking level override" })),
|
|
26
|
+
cwd: Type.Optional(Type.String({ description: "Working directory for this task. Default: current project." })),
|
|
27
|
+
tools: Type.Optional(Type.Array(Type.String(), { description: "Explicit tool allowlist (overrides the toolset)" })),
|
|
28
|
+
maxRuntimeMs: Type.Optional(Type.Number({ description: "Per-task timeout (ms)" })),
|
|
29
|
+
needs: Type.Optional(
|
|
30
|
+
Type.Array(Type.String(), {
|
|
31
|
+
description: "Ids of tasks this one waits for; their outputs are prepended to this prompt.",
|
|
32
|
+
}),
|
|
33
|
+
),
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
export const SubagentParams = Type.Object({
|
|
37
|
+
agent: Type.Optional(Type.String({ minLength: 1, description: "Name you invent for this subagent (single mode)" })),
|
|
38
|
+
task: Type.Optional(Type.String({ minLength: 1, description: "Task (single mode)" })),
|
|
39
|
+
prompt: Type.Optional(Type.String({ description: "System prompt for this agent (single mode)" })),
|
|
40
|
+
write: Type.Optional(Type.Boolean({ description: "true = write toolset; default false = read-only (single mode)" })),
|
|
41
|
+
tools: Type.Optional(
|
|
42
|
+
Type.Array(Type.String(), { description: "Explicit tool allowlist (overrides the toolset) (single mode)" }),
|
|
43
|
+
),
|
|
44
|
+
tasks: Type.Optional(Type.Array(TaskItem, { description: "Parallel tasks" })),
|
|
45
|
+
chain: Type.Optional(Type.Array(TaskItem, { description: "Sequential tasks; {previous} = prior output" })),
|
|
46
|
+
model: Type.Optional(Type.String({ description: "Model override (single mode)" })),
|
|
47
|
+
thinking: Type.Optional(StringEnum(THINKING_LEVELS, { description: "Thinking level override (single mode)" })),
|
|
48
|
+
cwd: Type.Optional(Type.String({ description: "Working directory (single mode). Default: current project." })),
|
|
49
|
+
concurrency: Type.Optional(
|
|
50
|
+
Type.Number({ description: `Parallel concurrency (default ${DEFAULT_CONCURRENCY}, max ${MAX_CONCURRENCY})` }),
|
|
51
|
+
),
|
|
52
|
+
maxRuntimeMs: Type.Optional(
|
|
53
|
+
Type.Number({
|
|
54
|
+
description: "Per-task timeout, ms. Omit for no cap (default): tasks run until done, stalled, or user-aborted.",
|
|
55
|
+
}),
|
|
56
|
+
),
|
|
57
|
+
background: Type.Optional(
|
|
58
|
+
Type.Boolean({ description: "Fire-and-forget: return immediately with a runId; you'll be notified on completion" }),
|
|
59
|
+
),
|
|
60
|
+
notifyPerTask: Type.Optional(
|
|
61
|
+
Type.Boolean({
|
|
62
|
+
description:
|
|
63
|
+
"Wake you (queued follow-up turn) as each task completes — background runs only, since blocking runs can't be woken mid-tool. Default false.",
|
|
64
|
+
}),
|
|
65
|
+
),
|
|
66
|
+
allowIntercom: Type.Optional(
|
|
67
|
+
Type.Boolean({ description: "Let children ask you questions, notify you, and message sibling subagents" }),
|
|
68
|
+
),
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
/** Derived from the schemas — single source of truth, no hand-maintained mirror. */
|
|
72
|
+
export type TaskInput = Static<typeof TaskItem>;
|
|
73
|
+
export type SubagentParamsShape = Static<typeof SubagentParams>;
|
|
74
|
+
|
|
75
|
+
export const RunIdParam = Type.Object({ runId: Type.String({ description: "Run id from subagent()" }) });
|
|
76
|
+
export const ResultParam = Type.Object({
|
|
77
|
+
runId: Type.String(),
|
|
78
|
+
taskId: Type.Optional(Type.String({ description: "Specific task id; defaults to all" })),
|
|
79
|
+
});
|
|
80
|
+
export const AwaitParam = Type.Object({
|
|
81
|
+
runId: Type.String(),
|
|
82
|
+
timeoutMs: Type.Optional(Type.Number({ description: "Max wait (ms); default: until finished" })),
|
|
83
|
+
});
|
|
84
|
+
export const ReplyParam = Type.Object({
|
|
85
|
+
runId: Type.String(),
|
|
86
|
+
taskId: Type.String(),
|
|
87
|
+
message: Type.String({ description: "Answer for the child" }),
|
|
88
|
+
});
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/** Shared run/task types for the subagent extension. No imports. */
|
|
2
|
+
|
|
3
|
+
export type RunMode = "single" | "parallel" | "chain";
|
|
4
|
+
export type TaskStatus = "queued" | "starting" | "running" | "awaiting_parent" | "completed" | "failed" | "aborted";
|
|
5
|
+
export type RunStatus = "queued" | "running" | "awaiting_parent" | "completed" | "failed" | "aborted";
|
|
6
|
+
|
|
7
|
+
export const TERMINAL: TaskStatus[] = ["completed", "failed", "aborted"];
|
|
8
|
+
|
|
9
|
+
/** Widget/command cap on rendered tasks; scheduler cap on spawned tasks. */
|
|
10
|
+
export const MAX_TASKS = 16;
|
|
11
|
+
|
|
12
|
+
export interface UsageStats {
|
|
13
|
+
input: number;
|
|
14
|
+
output: number;
|
|
15
|
+
cacheRead: number;
|
|
16
|
+
cacheWrite: number;
|
|
17
|
+
cost: number;
|
|
18
|
+
turns: number;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface TaskSnapshot {
|
|
22
|
+
id: string;
|
|
23
|
+
runId: string;
|
|
24
|
+
agent: string;
|
|
25
|
+
task: string;
|
|
26
|
+
cwd: string;
|
|
27
|
+
status: TaskStatus;
|
|
28
|
+
/** Resolved dependency edges (task ids). Empty/absent = wave 1. */
|
|
29
|
+
needs?: string[];
|
|
30
|
+
sessionId?: string;
|
|
31
|
+
sessionFile?: string;
|
|
32
|
+
startedAt?: number;
|
|
33
|
+
endedAt?: number;
|
|
34
|
+
toolCalls: number;
|
|
35
|
+
lastActivity?: string;
|
|
36
|
+
finalText?: string;
|
|
37
|
+
error?: string;
|
|
38
|
+
model?: string;
|
|
39
|
+
thinking?: string;
|
|
40
|
+
tools?: string[];
|
|
41
|
+
usage: UsageStats;
|
|
42
|
+
/** Sibling addresses for intercom tools (send_agent_message targets). */
|
|
43
|
+
roster?: string;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface RunSnapshot {
|
|
47
|
+
id: string;
|
|
48
|
+
mode: RunMode;
|
|
49
|
+
status: RunStatus;
|
|
50
|
+
background: boolean;
|
|
51
|
+
allowIntercom: boolean;
|
|
52
|
+
notifyPerTask: boolean;
|
|
53
|
+
createdAt: number;
|
|
54
|
+
startedAt?: number;
|
|
55
|
+
endedAt?: number;
|
|
56
|
+
concurrency: number;
|
|
57
|
+
tasks: TaskSnapshot[];
|
|
58
|
+
aggregateUsage: UsageStats;
|
|
59
|
+
/** True once the parent awaited this run — completion notices are redundant then. */
|
|
60
|
+
awaited?: boolean;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface RunDetails {
|
|
64
|
+
run: RunSnapshot;
|
|
65
|
+
background?: boolean;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export interface PendingReply {
|
|
69
|
+
resolve: (message: string) => void;
|
|
70
|
+
}
|