@gotgenes/pi-subagents 1.0.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/.markdownlint-cli2.yaml +19 -0
- package/.prettierignore +5 -0
- package/.release-please-manifest.json +3 -0
- package/AGENTS.md +85 -0
- package/CHANGELOG.md +495 -0
- package/LICENSE +21 -0
- package/README.md +528 -0
- package/dist/agent-manager.d.ts +108 -0
- package/dist/agent-manager.js +390 -0
- package/dist/agent-runner.d.ts +93 -0
- package/dist/agent-runner.js +428 -0
- package/dist/agent-types.d.ts +48 -0
- package/dist/agent-types.js +136 -0
- package/dist/context.d.ts +12 -0
- package/dist/context.js +56 -0
- package/dist/cross-extension-rpc.d.ts +46 -0
- package/dist/cross-extension-rpc.js +54 -0
- package/dist/custom-agents.d.ts +14 -0
- package/dist/custom-agents.js +127 -0
- package/dist/default-agents.d.ts +7 -0
- package/dist/default-agents.js +119 -0
- package/dist/env.d.ts +6 -0
- package/dist/env.js +28 -0
- package/dist/group-join.d.ts +32 -0
- package/dist/group-join.js +116 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +1731 -0
- package/dist/invocation-config.d.ts +22 -0
- package/dist/invocation-config.js +15 -0
- package/dist/memory.d.ts +49 -0
- package/dist/memory.js +151 -0
- package/dist/model-resolver.d.ts +19 -0
- package/dist/model-resolver.js +62 -0
- package/dist/output-file.d.ts +24 -0
- package/dist/output-file.js +86 -0
- package/dist/prompts.d.ts +29 -0
- package/dist/prompts.js +72 -0
- package/dist/schedule-store.d.ts +36 -0
- package/dist/schedule-store.js +144 -0
- package/dist/schedule.d.ts +109 -0
- package/dist/schedule.js +338 -0
- package/dist/settings.d.ts +66 -0
- package/dist/settings.js +130 -0
- package/dist/skill-loader.d.ts +24 -0
- package/dist/skill-loader.js +93 -0
- package/dist/types.d.ts +164 -0
- package/dist/types.js +5 -0
- package/dist/ui/agent-widget.d.ts +134 -0
- package/dist/ui/agent-widget.js +451 -0
- package/dist/ui/conversation-viewer.d.ts +35 -0
- package/dist/ui/conversation-viewer.js +252 -0
- package/dist/ui/schedule-menu.d.ts +16 -0
- package/dist/ui/schedule-menu.js +95 -0
- package/dist/usage.d.ts +50 -0
- package/dist/usage.js +49 -0
- package/dist/worktree.d.ts +36 -0
- package/dist/worktree.js +139 -0
- package/docs/decisions/0001-deferred-patches.md +75 -0
- package/package.json +68 -0
- package/prek.toml +24 -0
- package/release-please-config.json +22 -0
- package/src/agent-manager.ts +482 -0
- package/src/agent-runner.ts +625 -0
- package/src/agent-types.ts +164 -0
- package/src/context.ts +58 -0
- package/src/cross-extension-rpc.ts +95 -0
- package/src/custom-agents.ts +136 -0
- package/src/default-agents.ts +123 -0
- package/src/env.ts +33 -0
- package/src/group-join.ts +141 -0
- package/src/index.ts +1894 -0
- package/src/invocation-config.ts +40 -0
- package/src/memory.ts +165 -0
- package/src/model-resolver.ts +81 -0
- package/src/output-file.ts +96 -0
- package/src/prompts.ts +105 -0
- package/src/schedule-store.ts +143 -0
- package/src/schedule.ts +365 -0
- package/src/settings.ts +186 -0
- package/src/skill-loader.ts +102 -0
- package/src/types.ts +176 -0
- package/src/ui/agent-widget.ts +533 -0
- package/src/ui/conversation-viewer.ts +261 -0
- package/src/ui/schedule-menu.ts +104 -0
- package/src/usage.ts +60 -0
- package/src/worktree.ts +162 -0
package/dist/settings.js
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
// Persistence for pi-subagents operational settings.
|
|
2
|
+
// - Global: ~/.pi/agent/subagents.json (via getAgentDir()) — manual defaults, never written here
|
|
3
|
+
// - Project: <cwd>/.pi/subagents.json — written by /agents → Settings; overrides global on load
|
|
4
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
5
|
+
import { dirname, join } from "node:path";
|
|
6
|
+
import { getAgentDir } from "@earendil-works/pi-coding-agent";
|
|
7
|
+
const VALID_JOIN_MODES = new Set(["async", "group", "smart"]);
|
|
8
|
+
// Sanity ceilings — prevent hand-edited configs from asking for values that
|
|
9
|
+
// make no operational sense (e.g. 1e6 concurrent subagents). Permissive enough
|
|
10
|
+
// that any realistic power-user setting passes through.
|
|
11
|
+
const MAX_CONCURRENT_CEILING = 1024;
|
|
12
|
+
const MAX_TURNS_CEILING = 10_000;
|
|
13
|
+
const GRACE_TURNS_CEILING = 1_000;
|
|
14
|
+
/** Drop fields that don't match the expected shape. Silent — garbage becomes absent. */
|
|
15
|
+
function sanitize(raw) {
|
|
16
|
+
if (!raw || typeof raw !== "object")
|
|
17
|
+
return {};
|
|
18
|
+
const r = raw;
|
|
19
|
+
const out = {};
|
|
20
|
+
if (Number.isInteger(r.maxConcurrent) &&
|
|
21
|
+
r.maxConcurrent >= 1 &&
|
|
22
|
+
r.maxConcurrent <= MAX_CONCURRENT_CEILING) {
|
|
23
|
+
out.maxConcurrent = r.maxConcurrent;
|
|
24
|
+
}
|
|
25
|
+
if (Number.isInteger(r.defaultMaxTurns) &&
|
|
26
|
+
r.defaultMaxTurns >= 0 &&
|
|
27
|
+
r.defaultMaxTurns <= MAX_TURNS_CEILING) {
|
|
28
|
+
out.defaultMaxTurns = r.defaultMaxTurns;
|
|
29
|
+
}
|
|
30
|
+
if (Number.isInteger(r.graceTurns) &&
|
|
31
|
+
r.graceTurns >= 1 &&
|
|
32
|
+
r.graceTurns <= GRACE_TURNS_CEILING) {
|
|
33
|
+
out.graceTurns = r.graceTurns;
|
|
34
|
+
}
|
|
35
|
+
if (typeof r.defaultJoinMode === "string" && VALID_JOIN_MODES.has(r.defaultJoinMode)) {
|
|
36
|
+
out.defaultJoinMode = r.defaultJoinMode;
|
|
37
|
+
}
|
|
38
|
+
if (typeof r.schedulingEnabled === "boolean") {
|
|
39
|
+
out.schedulingEnabled = r.schedulingEnabled;
|
|
40
|
+
}
|
|
41
|
+
return out;
|
|
42
|
+
}
|
|
43
|
+
function globalPath() {
|
|
44
|
+
return join(getAgentDir(), "subagents.json");
|
|
45
|
+
}
|
|
46
|
+
function projectPath(cwd) {
|
|
47
|
+
return join(cwd, ".pi", "subagents.json");
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Read a settings file. Missing file is silent (returns `{}`). A file that
|
|
51
|
+
* exists but can't be parsed emits a warning to stderr so users aren't
|
|
52
|
+
* silently reverted to defaults — and still returns `{}` so startup proceeds.
|
|
53
|
+
*/
|
|
54
|
+
function readSettingsFile(path) {
|
|
55
|
+
if (!existsSync(path))
|
|
56
|
+
return {};
|
|
57
|
+
try {
|
|
58
|
+
return sanitize(JSON.parse(readFileSync(path, "utf-8")));
|
|
59
|
+
}
|
|
60
|
+
catch (err) {
|
|
61
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
62
|
+
console.warn(`[pi-subagents] Ignoring malformed settings at ${path}: ${reason}`);
|
|
63
|
+
return {};
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
/** Load merged settings: global provides defaults, project overrides. */
|
|
67
|
+
export function loadSettings(cwd = process.cwd()) {
|
|
68
|
+
return { ...readSettingsFile(globalPath()), ...readSettingsFile(projectPath(cwd)) };
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Write project-local settings. Global is never touched from code.
|
|
72
|
+
* Returns `true` on success, `false` if the write (or mkdir) failed so the
|
|
73
|
+
* caller can surface a warning — persistence isn't fatal but isn't silent.
|
|
74
|
+
*/
|
|
75
|
+
export function saveSettings(s, cwd = process.cwd()) {
|
|
76
|
+
const path = projectPath(cwd);
|
|
77
|
+
try {
|
|
78
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
79
|
+
writeFileSync(path, JSON.stringify(s, null, 2), "utf-8");
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
/** Apply persisted settings to the in-memory state via caller-supplied setters. */
|
|
87
|
+
export function applySettings(s, appliers) {
|
|
88
|
+
if (typeof s.maxConcurrent === "number")
|
|
89
|
+
appliers.setMaxConcurrent(s.maxConcurrent);
|
|
90
|
+
if (typeof s.defaultMaxTurns === "number")
|
|
91
|
+
appliers.setDefaultMaxTurns(s.defaultMaxTurns);
|
|
92
|
+
if (typeof s.graceTurns === "number")
|
|
93
|
+
appliers.setGraceTurns(s.graceTurns);
|
|
94
|
+
if (s.defaultJoinMode)
|
|
95
|
+
appliers.setDefaultJoinMode(s.defaultJoinMode);
|
|
96
|
+
if (typeof s.schedulingEnabled === "boolean")
|
|
97
|
+
appliers.setSchedulingEnabled(s.schedulingEnabled);
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Format the user-facing toast for a settings mutation. Pure function —
|
|
101
|
+
* routes the success/failure of `saveSettings` into the right message + level
|
|
102
|
+
* so the UI layer (index.ts) stays a thin wire between input and notification.
|
|
103
|
+
*/
|
|
104
|
+
export function persistToastFor(successMsg, persisted) {
|
|
105
|
+
return persisted
|
|
106
|
+
? { message: successMsg, level: "info" }
|
|
107
|
+
: { message: `${successMsg} (session only; failed to persist)`, level: "warning" };
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Load merged settings, apply them to in-memory state, and emit the
|
|
111
|
+
* `subagents:settings_loaded` lifecycle event. Returns the loaded settings so
|
|
112
|
+
* callers can log/inspect. Extension init wires this once.
|
|
113
|
+
*/
|
|
114
|
+
export function applyAndEmitLoaded(appliers, emit, cwd = process.cwd()) {
|
|
115
|
+
const settings = loadSettings(cwd);
|
|
116
|
+
applySettings(settings, appliers);
|
|
117
|
+
emit("subagents:settings_loaded", { settings });
|
|
118
|
+
return settings;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Persist a settings snapshot, emit the `subagents:settings_changed` event
|
|
122
|
+
* (regardless of persist outcome so listeners see the in-memory change), and
|
|
123
|
+
* return the toast the UI should display. Event payload carries the `persisted`
|
|
124
|
+
* flag so listeners can react to write failures.
|
|
125
|
+
*/
|
|
126
|
+
export function saveAndEmitChanged(snapshot, successMsg, emit, cwd = process.cwd()) {
|
|
127
|
+
const persisted = saveSettings(snapshot, cwd);
|
|
128
|
+
emit("subagents:settings_changed", { settings: snapshot, persisted });
|
|
129
|
+
return persistToastFor(successMsg, persisted);
|
|
130
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* skill-loader.ts — Preload named skills.
|
|
3
|
+
*
|
|
4
|
+
* Roots, in precedence order:
|
|
5
|
+
* - <cwd>/.pi/skills (project, Pi's standard)
|
|
6
|
+
* - <cwd>/.agents/skills (project, cross-tool Agent Skills spec — https://agentskills.io)
|
|
7
|
+
* - getAgentDir()/skills (user, default ~/.pi/agent/skills — Pi's standard)
|
|
8
|
+
* - ~/.agents/skills (user, cross-tool Agent Skills spec)
|
|
9
|
+
* - ~/.pi/skills (legacy global, pre-Pi)
|
|
10
|
+
*
|
|
11
|
+
* Layout per root:
|
|
12
|
+
* - <root>/<name>.md (flat file at the top level)
|
|
13
|
+
* - <root>/.../<name>/SKILL.md (directory skill, may be nested — Pi's standard)
|
|
14
|
+
*
|
|
15
|
+
* Recursion skips dotfile entries and node_modules. A directory that itself contains
|
|
16
|
+
* SKILL.md is a skill — we don't descend into it (Pi: skills don't nest).
|
|
17
|
+
*
|
|
18
|
+
* Symlinks are rejected for security (deviation from Pi, which follows them).
|
|
19
|
+
*/
|
|
20
|
+
export interface PreloadedSkill {
|
|
21
|
+
name: string;
|
|
22
|
+
content: string;
|
|
23
|
+
}
|
|
24
|
+
export declare function preloadSkills(skillNames: string[], cwd: string): PreloadedSkill[];
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* skill-loader.ts — Preload named skills.
|
|
3
|
+
*
|
|
4
|
+
* Roots, in precedence order:
|
|
5
|
+
* - <cwd>/.pi/skills (project, Pi's standard)
|
|
6
|
+
* - <cwd>/.agents/skills (project, cross-tool Agent Skills spec — https://agentskills.io)
|
|
7
|
+
* - getAgentDir()/skills (user, default ~/.pi/agent/skills — Pi's standard)
|
|
8
|
+
* - ~/.agents/skills (user, cross-tool Agent Skills spec)
|
|
9
|
+
* - ~/.pi/skills (legacy global, pre-Pi)
|
|
10
|
+
*
|
|
11
|
+
* Layout per root:
|
|
12
|
+
* - <root>/<name>.md (flat file at the top level)
|
|
13
|
+
* - <root>/.../<name>/SKILL.md (directory skill, may be nested — Pi's standard)
|
|
14
|
+
*
|
|
15
|
+
* Recursion skips dotfile entries and node_modules. A directory that itself contains
|
|
16
|
+
* SKILL.md is a skill — we don't descend into it (Pi: skills don't nest).
|
|
17
|
+
*
|
|
18
|
+
* Symlinks are rejected for security (deviation from Pi, which follows them).
|
|
19
|
+
*/
|
|
20
|
+
import { existsSync, readdirSync } from "node:fs";
|
|
21
|
+
import { homedir } from "node:os";
|
|
22
|
+
import { join } from "node:path";
|
|
23
|
+
import { getAgentDir } from "@earendil-works/pi-coding-agent";
|
|
24
|
+
import { isSymlink, isUnsafeName, safeReadFile } from "./memory.js";
|
|
25
|
+
export function preloadSkills(skillNames, cwd) {
|
|
26
|
+
return skillNames.map((name) => ({ name, content: loadSkillContent(name, cwd) }));
|
|
27
|
+
}
|
|
28
|
+
function loadSkillContent(name, cwd) {
|
|
29
|
+
if (isUnsafeName(name)) {
|
|
30
|
+
return `(Skill "${name}" skipped: name contains path traversal characters)`;
|
|
31
|
+
}
|
|
32
|
+
const roots = [
|
|
33
|
+
join(cwd, ".pi", "skills"), // project — Pi standard
|
|
34
|
+
join(cwd, ".agents", "skills"), // project — Agent Skills spec
|
|
35
|
+
join(getAgentDir(), "skills"), // user — Pi standard
|
|
36
|
+
join(homedir(), ".agents", "skills"), // user — Agent Skills spec
|
|
37
|
+
join(homedir(), ".pi", "skills"), // legacy global, pre-Pi
|
|
38
|
+
];
|
|
39
|
+
for (const root of roots) {
|
|
40
|
+
const content = findInRoot(root, name);
|
|
41
|
+
if (content !== undefined)
|
|
42
|
+
return content;
|
|
43
|
+
}
|
|
44
|
+
return `(Skill "${name}" not found in .pi/skills/, .agents/skills/, or global skill locations)`;
|
|
45
|
+
}
|
|
46
|
+
function findInRoot(root, name) {
|
|
47
|
+
if (isSymlink(root))
|
|
48
|
+
return undefined; // reject symlinked roots entirely
|
|
49
|
+
const flat = safeReadFile(join(root, `${name}.md`))?.trim();
|
|
50
|
+
if (flat !== undefined)
|
|
51
|
+
return flat;
|
|
52
|
+
return findSkillDirectory(root, name);
|
|
53
|
+
}
|
|
54
|
+
/** BFS under `root` for a directory named `name` containing `SKILL.md`. Pi-conforming filters. */
|
|
55
|
+
function findSkillDirectory(root, name) {
|
|
56
|
+
if (!existsSync(root))
|
|
57
|
+
return undefined;
|
|
58
|
+
const queue = [root];
|
|
59
|
+
while (queue.length > 0) {
|
|
60
|
+
const current = queue.shift();
|
|
61
|
+
if (current === undefined)
|
|
62
|
+
continue;
|
|
63
|
+
let entries;
|
|
64
|
+
try {
|
|
65
|
+
entries = readdirSync(current, { withFileTypes: true });
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
// Deterministic byte-order traversal — locale-independent.
|
|
71
|
+
entries.sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
|
|
72
|
+
for (const entry of entries) {
|
|
73
|
+
if (!entry.isDirectory())
|
|
74
|
+
continue;
|
|
75
|
+
if (entry.name.startsWith(".") || entry.name === "node_modules")
|
|
76
|
+
continue;
|
|
77
|
+
// Symlinked dirs already filtered by entry.isDirectory() — Dirent uses lstat semantics.
|
|
78
|
+
const path = join(current, entry.name);
|
|
79
|
+
const skillMd = join(path, "SKILL.md");
|
|
80
|
+
const isSkillDir = existsSync(skillMd);
|
|
81
|
+
if (isSkillDir) {
|
|
82
|
+
if (entry.name === name) {
|
|
83
|
+
const content = safeReadFile(skillMd)?.trim();
|
|
84
|
+
if (content !== undefined)
|
|
85
|
+
return content;
|
|
86
|
+
}
|
|
87
|
+
continue; // Pi rule: skills don't nest — don't descend into a skill dir
|
|
88
|
+
}
|
|
89
|
+
queue.push(path);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return undefined;
|
|
93
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* types.ts — Type definitions for the subagent system.
|
|
3
|
+
*/
|
|
4
|
+
import type { ThinkingLevel } from "@earendil-works/pi-ai";
|
|
5
|
+
import type { AgentSession } from "@earendil-works/pi-coding-agent";
|
|
6
|
+
import type { LifetimeUsage } from "./usage.js";
|
|
7
|
+
export type { ThinkingLevel };
|
|
8
|
+
/** Agent type: any string name (built-in defaults or user-defined). */
|
|
9
|
+
export type SubagentType = string;
|
|
10
|
+
/** Names of the three embedded default agents. */
|
|
11
|
+
export declare const DEFAULT_AGENT_NAMES: readonly ["general-purpose", "Explore", "Plan"];
|
|
12
|
+
/** Memory scope for persistent agent memory. */
|
|
13
|
+
export type MemoryScope = "user" | "project" | "local";
|
|
14
|
+
/** Isolation mode for agent execution. */
|
|
15
|
+
export type IsolationMode = "worktree";
|
|
16
|
+
/** Unified agent configuration — used for both default and user-defined agents. */
|
|
17
|
+
export interface AgentConfig {
|
|
18
|
+
name: string;
|
|
19
|
+
displayName?: string;
|
|
20
|
+
description: string;
|
|
21
|
+
builtinToolNames?: string[];
|
|
22
|
+
/** Tool denylist — these tools are removed even if `builtinToolNames` or extensions include them. */
|
|
23
|
+
disallowedTools?: string[];
|
|
24
|
+
/** true = inherit all, string[] = only listed, false = none */
|
|
25
|
+
extensions: true | string[] | false;
|
|
26
|
+
/** true = inherit all, string[] = only listed, false = none */
|
|
27
|
+
skills: true | string[] | false;
|
|
28
|
+
model?: string;
|
|
29
|
+
thinking?: ThinkingLevel;
|
|
30
|
+
maxTurns?: number;
|
|
31
|
+
systemPrompt: string;
|
|
32
|
+
promptMode: "replace" | "append";
|
|
33
|
+
/** Default for spawn: fork parent conversation. undefined = caller decides. */
|
|
34
|
+
inheritContext?: boolean;
|
|
35
|
+
/** Default for spawn: run in background. undefined = caller decides. */
|
|
36
|
+
runInBackground?: boolean;
|
|
37
|
+
/** Default for spawn: no extension tools. undefined = caller decides. */
|
|
38
|
+
isolated?: boolean;
|
|
39
|
+
/** Persistent memory scope — agents with memory get a persistent directory and MEMORY.md */
|
|
40
|
+
memory?: MemoryScope;
|
|
41
|
+
/** Isolation mode — "worktree" runs the agent in a temporary git worktree */
|
|
42
|
+
isolation?: IsolationMode;
|
|
43
|
+
/** true = this is an embedded default agent (informational) */
|
|
44
|
+
isDefault?: boolean;
|
|
45
|
+
/** false = agent is hidden from the registry */
|
|
46
|
+
enabled?: boolean;
|
|
47
|
+
/** Where this agent was loaded from */
|
|
48
|
+
source?: "default" | "project" | "global";
|
|
49
|
+
}
|
|
50
|
+
export type JoinMode = 'async' | 'group' | 'smart';
|
|
51
|
+
export interface AgentRecord {
|
|
52
|
+
id: string;
|
|
53
|
+
type: SubagentType;
|
|
54
|
+
description: string;
|
|
55
|
+
status: "queued" | "running" | "completed" | "steered" | "aborted" | "stopped" | "error";
|
|
56
|
+
result?: string;
|
|
57
|
+
error?: string;
|
|
58
|
+
toolUses: number;
|
|
59
|
+
startedAt: number;
|
|
60
|
+
completedAt?: number;
|
|
61
|
+
session?: AgentSession;
|
|
62
|
+
abortController?: AbortController;
|
|
63
|
+
promise?: Promise<string>;
|
|
64
|
+
groupId?: string;
|
|
65
|
+
joinMode?: JoinMode;
|
|
66
|
+
/** Set when result was already consumed via get_subagent_result — suppresses completion notification. */
|
|
67
|
+
resultConsumed?: boolean;
|
|
68
|
+
/** Steering messages queued before the session was ready. */
|
|
69
|
+
pendingSteers?: string[];
|
|
70
|
+
/** Worktree info if the agent is running in an isolated worktree. */
|
|
71
|
+
worktree?: {
|
|
72
|
+
path: string;
|
|
73
|
+
branch: string;
|
|
74
|
+
};
|
|
75
|
+
/** Worktree cleanup result after agent completion. */
|
|
76
|
+
worktreeResult?: {
|
|
77
|
+
hasChanges: boolean;
|
|
78
|
+
branch?: string;
|
|
79
|
+
};
|
|
80
|
+
/** The tool_use_id from the original Agent tool call. */
|
|
81
|
+
toolCallId?: string;
|
|
82
|
+
/** Path to the streaming output transcript file. */
|
|
83
|
+
outputFile?: string;
|
|
84
|
+
/** Cleanup function for the output file stream subscription. */
|
|
85
|
+
outputCleanup?: () => void;
|
|
86
|
+
/**
|
|
87
|
+
* Lifetime usage breakdown, accumulated via `message_end` events. Survives
|
|
88
|
+
* compaction. Total = input + output + cacheWrite (cacheRead deliberately
|
|
89
|
+
* excluded — see issue #38). Initialized to zeros at spawn.
|
|
90
|
+
*/
|
|
91
|
+
lifetimeUsage: LifetimeUsage;
|
|
92
|
+
/** Number of times this agent's session has compacted. Initialized to 0 at spawn. */
|
|
93
|
+
compactionCount: number;
|
|
94
|
+
/** Resolved spawn params, captured for UI display. Fixed at spawn time. */
|
|
95
|
+
invocation?: AgentInvocation;
|
|
96
|
+
}
|
|
97
|
+
export interface AgentInvocation {
|
|
98
|
+
/** Short display name, e.g. "haiku" — only set when different from parent. */
|
|
99
|
+
modelName?: string;
|
|
100
|
+
thinking?: ThinkingLevel;
|
|
101
|
+
maxTurns?: number;
|
|
102
|
+
isolated?: boolean;
|
|
103
|
+
inheritContext?: boolean;
|
|
104
|
+
runInBackground?: boolean;
|
|
105
|
+
isolation?: IsolationMode;
|
|
106
|
+
}
|
|
107
|
+
/** Details attached to custom notification messages for visual rendering. */
|
|
108
|
+
export interface NotificationDetails {
|
|
109
|
+
id: string;
|
|
110
|
+
description: string;
|
|
111
|
+
status: string;
|
|
112
|
+
toolUses: number;
|
|
113
|
+
turnCount: number;
|
|
114
|
+
maxTurns?: number;
|
|
115
|
+
totalTokens: number;
|
|
116
|
+
durationMs: number;
|
|
117
|
+
outputFile?: string;
|
|
118
|
+
error?: string;
|
|
119
|
+
resultPreview: string;
|
|
120
|
+
/** Additional agents in a group notification. */
|
|
121
|
+
others?: NotificationDetails[];
|
|
122
|
+
}
|
|
123
|
+
export interface EnvInfo {
|
|
124
|
+
isGitRepo: boolean;
|
|
125
|
+
branch: string;
|
|
126
|
+
platform: string;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* A subagent spawn registered to fire on a schedule.
|
|
130
|
+
*
|
|
131
|
+
* Stored at `<cwd>/.pi/subagent-schedules/<sessionId>.json`. Session-scoped:
|
|
132
|
+
* survives `/resume` but resets on `/new`, mirroring pi-chonky-tasks.
|
|
133
|
+
*/
|
|
134
|
+
export interface ScheduledSubagent {
|
|
135
|
+
id: string;
|
|
136
|
+
/** Unique within store. Defaults to `description`. */
|
|
137
|
+
name: string;
|
|
138
|
+
description: string;
|
|
139
|
+
/** Raw user input — cron expr | "+10m" | ISO | "5m". */
|
|
140
|
+
schedule: string;
|
|
141
|
+
scheduleType: "cron" | "once" | "interval";
|
|
142
|
+
/** Computed at create time for interval/once. */
|
|
143
|
+
intervalMs?: number;
|
|
144
|
+
subagent_type: SubagentType;
|
|
145
|
+
prompt: string;
|
|
146
|
+
model?: string;
|
|
147
|
+
thinking?: ThinkingLevel;
|
|
148
|
+
max_turns?: number;
|
|
149
|
+
isolated?: boolean;
|
|
150
|
+
isolation?: IsolationMode;
|
|
151
|
+
enabled: boolean;
|
|
152
|
+
/** ISO timestamp. */
|
|
153
|
+
createdAt: string;
|
|
154
|
+
lastRun?: string;
|
|
155
|
+
lastStatus?: "success" | "error" | "running";
|
|
156
|
+
/** Refreshed on every fire and on store load. */
|
|
157
|
+
nextRun?: string;
|
|
158
|
+
runCount: number;
|
|
159
|
+
}
|
|
160
|
+
export interface ScheduleStoreData {
|
|
161
|
+
/** For future migrations. */
|
|
162
|
+
version: 1;
|
|
163
|
+
jobs: ScheduledSubagent[];
|
|
164
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* agent-widget.ts — Persistent widget showing running/completed agents above the editor.
|
|
3
|
+
*
|
|
4
|
+
* Displays a tree of agents with animated spinners, live stats, and activity descriptions.
|
|
5
|
+
* Uses the callback form of setWidget for themed rendering.
|
|
6
|
+
*/
|
|
7
|
+
import type { AgentManager } from "../agent-manager.js";
|
|
8
|
+
import type { AgentInvocation, SubagentType } from "../types.js";
|
|
9
|
+
import { type LifetimeUsage, type SessionLike } from "../usage.js";
|
|
10
|
+
/** Braille spinner frames for animated running indicator. */
|
|
11
|
+
export declare const SPINNER: string[];
|
|
12
|
+
/** Statuses that indicate an error/non-success outcome (used for linger behavior and icon rendering). */
|
|
13
|
+
export declare const ERROR_STATUSES: Set<string>;
|
|
14
|
+
export type Theme = {
|
|
15
|
+
fg(color: string, text: string): string;
|
|
16
|
+
bold(text: string): string;
|
|
17
|
+
};
|
|
18
|
+
export type UICtx = {
|
|
19
|
+
setStatus(key: string, text: string | undefined): void;
|
|
20
|
+
setWidget(key: string, content: undefined | ((tui: any, theme: Theme) => {
|
|
21
|
+
render(): string[];
|
|
22
|
+
invalidate(): void;
|
|
23
|
+
}), options?: {
|
|
24
|
+
placement?: "aboveEditor" | "belowEditor";
|
|
25
|
+
}): void;
|
|
26
|
+
};
|
|
27
|
+
/** Per-agent live activity state. */
|
|
28
|
+
export interface AgentActivity {
|
|
29
|
+
activeTools: Map<string, string>;
|
|
30
|
+
toolUses: number;
|
|
31
|
+
responseText: string;
|
|
32
|
+
session?: SessionLike;
|
|
33
|
+
/** Current turn count. */
|
|
34
|
+
turnCount: number;
|
|
35
|
+
/** Effective max turns for this agent (undefined = unlimited). */
|
|
36
|
+
maxTurns?: number;
|
|
37
|
+
/** Lifetime usage breakdown — see LifetimeUsage docs. */
|
|
38
|
+
lifetimeUsage: LifetimeUsage;
|
|
39
|
+
}
|
|
40
|
+
/** Metadata attached to Agent tool results for custom rendering. */
|
|
41
|
+
export interface AgentDetails {
|
|
42
|
+
displayName: string;
|
|
43
|
+
description: string;
|
|
44
|
+
subagentType: string;
|
|
45
|
+
toolUses: number;
|
|
46
|
+
tokens: string;
|
|
47
|
+
durationMs: number;
|
|
48
|
+
status: "queued" | "running" | "completed" | "steered" | "aborted" | "stopped" | "error" | "background";
|
|
49
|
+
/** Human-readable description of what the agent is currently doing. */
|
|
50
|
+
activity?: string;
|
|
51
|
+
/** Current spinner frame index (for animated running indicator). */
|
|
52
|
+
spinnerFrame?: number;
|
|
53
|
+
/** Short model name if different from parent (e.g. "haiku", "sonnet"). */
|
|
54
|
+
modelName?: string;
|
|
55
|
+
/** Notable config tags (e.g. ["thinking: high", "isolated"]). */
|
|
56
|
+
tags?: string[];
|
|
57
|
+
/** Current turn count. */
|
|
58
|
+
turnCount?: number;
|
|
59
|
+
/** Effective max turns (undefined = unlimited). */
|
|
60
|
+
maxTurns?: number;
|
|
61
|
+
agentId?: string;
|
|
62
|
+
error?: string;
|
|
63
|
+
}
|
|
64
|
+
/** Format a token count compactly: "33.8k token", "1.2M token". */
|
|
65
|
+
export declare function formatTokens(count: number): string;
|
|
66
|
+
/**
|
|
67
|
+
* Token count with optional context-fill % and compaction-count annotations.
|
|
68
|
+
* Thresholds for percent: <70% dim, 70–85% warning, ≥85% error.
|
|
69
|
+
* Compaction count rendered as `↻N` in dim.
|
|
70
|
+
*
|
|
71
|
+
* "12.3k token" — no annotations
|
|
72
|
+
* "12.3k token (45%)" — percent only
|
|
73
|
+
* "12.3k token (↻2)" — compactions only (e.g. right after compact)
|
|
74
|
+
* "12.3k token (45% · ↻2)" — both
|
|
75
|
+
*/
|
|
76
|
+
export declare function formatSessionTokens(tokens: number, percent: number | null, theme: Theme, compactions?: number): string;
|
|
77
|
+
/** Format turn count with optional max limit: "⟳5≤30" or "⟳5". */
|
|
78
|
+
export declare function formatTurns(turnCount: number, maxTurns?: number | null): string;
|
|
79
|
+
/** Format milliseconds as human-readable duration. */
|
|
80
|
+
export declare function formatMs(ms: number): string;
|
|
81
|
+
/** Format duration from start/completed timestamps. */
|
|
82
|
+
export declare function formatDuration(startedAt: number, completedAt?: number): string;
|
|
83
|
+
/** Get display name for any agent type (built-in or custom). */
|
|
84
|
+
export declare function getDisplayName(type: SubagentType): string;
|
|
85
|
+
/** Short label for prompt mode: "twin" for append, nothing for replace (the default). */
|
|
86
|
+
export declare function getPromptModeLabel(type: SubagentType): string | undefined;
|
|
87
|
+
/** Mode label is not included — callers add it where they want it. */
|
|
88
|
+
export declare function buildInvocationTags(invocation: AgentInvocation | undefined): {
|
|
89
|
+
modelName?: string;
|
|
90
|
+
tags: string[];
|
|
91
|
+
};
|
|
92
|
+
/** Build a human-readable activity string from currently-running tools or response text. */
|
|
93
|
+
export declare function describeActivity(activeTools: Map<string, string>, responseText?: string): string;
|
|
94
|
+
export declare class AgentWidget {
|
|
95
|
+
private manager;
|
|
96
|
+
private agentActivity;
|
|
97
|
+
private uiCtx;
|
|
98
|
+
private widgetFrame;
|
|
99
|
+
private widgetInterval;
|
|
100
|
+
/** Tracks how many turns each finished agent has survived. Key: agent ID, Value: turns since finished. */
|
|
101
|
+
private finishedTurnAge;
|
|
102
|
+
/** How many extra turns errors/aborted agents linger (completed agents clear after 1 turn). */
|
|
103
|
+
private static readonly ERROR_LINGER_TURNS;
|
|
104
|
+
/** Whether the widget callback is currently registered with the TUI. */
|
|
105
|
+
private widgetRegistered;
|
|
106
|
+
/** Cached TUI reference from widget factory callback, used for requestRender(). */
|
|
107
|
+
private tui;
|
|
108
|
+
/** Last status bar text, used to avoid redundant setStatus calls. */
|
|
109
|
+
private lastStatusText;
|
|
110
|
+
constructor(manager: AgentManager, agentActivity: Map<string, AgentActivity>);
|
|
111
|
+
/** Set the UI context (grabbed from first tool execution). */
|
|
112
|
+
setUICtx(ctx: UICtx): void;
|
|
113
|
+
/**
|
|
114
|
+
* Called on each new turn (tool_execution_start).
|
|
115
|
+
* Ages finished agents and clears those that have lingered long enough.
|
|
116
|
+
*/
|
|
117
|
+
onTurnStart(): void;
|
|
118
|
+
/** Ensure the widget update timer is running. */
|
|
119
|
+
ensureTimer(): void;
|
|
120
|
+
/** Check if a finished agent should still be shown in the widget. */
|
|
121
|
+
private shouldShowFinished;
|
|
122
|
+
/** Record an agent as finished (call when agent completes). */
|
|
123
|
+
markFinished(agentId: string): void;
|
|
124
|
+
/** Render a finished agent line. */
|
|
125
|
+
private renderFinishedLine;
|
|
126
|
+
/**
|
|
127
|
+
* Render the widget content. Called from the registered widget's render() callback,
|
|
128
|
+
* reading live state each time instead of capturing it in a closure.
|
|
129
|
+
*/
|
|
130
|
+
private renderWidget;
|
|
131
|
+
/** Force an immediate widget update. */
|
|
132
|
+
update(): void;
|
|
133
|
+
dispose(): void;
|
|
134
|
+
}
|