pi-subagents 0.45.2 → 0.47.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 +47 -0
- package/README.md +2 -0
- package/docs/agents.md +342 -0
- package/docs/configuration.md +328 -0
- package/docs/extension-api.md +308 -0
- package/docs/missions.md +119 -0
- package/docs/models.md +192 -0
- package/docs/observability.md +174 -0
- package/docs/tool-reference.md +343 -0
- package/docs/watchdog.md +176 -0
- package/docs/workflows.md +163 -0
- package/package.json +4 -2
- package/skills/pi-subagents/references/execution-controls.md +6 -6
- package/skills/pi-subagents/references/management-authoring-rpc.md +1 -1
- package/src/agents/agents.ts +17 -8
- package/src/agents/frontmatter.ts +7 -3
- package/src/agents/skills.ts +2 -9
- package/src/api/project-panes.ts +30 -0
- package/src/extension/config.ts +18 -1
- package/src/extension/fanout-child.ts +5 -4
- package/src/extension/index.ts +66 -19
- package/src/extension/rpc.ts +3 -6
- package/src/extension/schemas.ts +28 -7
- package/src/extension/subagent-guide.ts +39 -0
- package/src/extension/tool-description.ts +30 -12
- package/src/inspectors/herdr/project-panes.ts +459 -63
- package/src/missions/actions.ts +25 -2
- package/src/missions/lifecycle.ts +21 -2
- package/src/missions/store.ts +79 -2
- package/src/missions/types.ts +33 -0
- package/src/missions/workflow-state.ts +19 -13
- package/src/runs/background/async-execution.ts +17 -6
- package/src/runs/background/async-job-tracker.ts +15 -0
- package/src/runs/background/async-resume.ts +19 -3
- package/src/runs/background/async-status.ts +6 -1
- package/src/runs/background/completion-replay.ts +267 -0
- package/src/runs/background/control-channel.ts +36 -0
- package/src/runs/background/result-watcher.ts +28 -6
- package/src/runs/background/scheduled-runs.ts +2 -1
- package/src/runs/background/stale-run-reconciler.ts +2 -21
- package/src/runs/background/subagent-runner.ts +47 -6
- package/src/runs/background/wait-completions.ts +39 -5
- package/src/runs/background/wait-subscriptions.ts +18 -3
- package/src/runs/foreground/async-steering-action.ts +1 -1
- package/src/runs/foreground/chain-execution.ts +3 -0
- package/src/runs/foreground/execution.ts +7 -0
- package/src/runs/foreground/foreground-history.ts +137 -0
- package/src/runs/foreground/subagent-executor.ts +403 -54
- package/src/runs/foreground/workflow-foreground-steering.ts +187 -0
- package/src/runs/shared/dynamic-fanout.ts +1 -1
- package/src/runs/shared/model-fallback.ts +8 -4
- package/src/runs/shared/model-scope.ts +12 -2
- package/src/runs/shared/parallel-utils.ts +1 -0
- package/src/runs/shared/worktree.ts +3 -2
- package/src/shared/artifacts.ts +14 -14
- package/src/shared/display-text.ts +100 -0
- package/src/shared/fork-context.ts +13 -0
- package/src/shared/formatters.ts +4 -6
- package/src/shared/prompt-resources.ts +51 -0
- package/src/shared/settings.ts +15 -2
- package/src/shared/types.ts +41 -2
- package/src/shared/utf8.ts +11 -0
- package/src/shared/utils.ts +43 -33
- package/src/slash/prompt-workflows.ts +2 -15
- package/src/slash/slash-commands.ts +22 -2
- package/src/tui/fleet-status.ts +22 -12
- package/src/tui/fleet.ts +135 -25
- package/src/tui/render.ts +150 -33
- package/src/watchdog/change-signature.ts +4 -3
- package/src/workflows/scripted-workflow.ts +167 -10
package/src/shared/types.ts
CHANGED
|
@@ -969,6 +969,8 @@ export interface WaitCompletion {
|
|
|
969
969
|
mode?: string;
|
|
970
970
|
state?: string;
|
|
971
971
|
success?: boolean;
|
|
972
|
+
/** Versioned bounded output archive retained with the durable completion replay. */
|
|
973
|
+
archivePath?: string;
|
|
972
974
|
results?: WaitCompletionChild[];
|
|
973
975
|
}
|
|
974
976
|
|
|
@@ -1045,6 +1047,7 @@ export interface Details {
|
|
|
1045
1047
|
operation: "run" | "status";
|
|
1046
1048
|
key: string;
|
|
1047
1049
|
state: "started" | "completed" | "failed" | "reused";
|
|
1050
|
+
agent?: string;
|
|
1048
1051
|
runId?: string;
|
|
1049
1052
|
phase?: string;
|
|
1050
1053
|
label?: string;
|
|
@@ -1538,6 +1541,12 @@ export interface ForegroundChildControl {
|
|
|
1538
1541
|
|
|
1539
1542
|
export interface ForegroundRunControl {
|
|
1540
1543
|
runId: string;
|
|
1544
|
+
/** Workflow shell that owns this live foreground child, when applicable. */
|
|
1545
|
+
parentWorkflowRunId?: string;
|
|
1546
|
+
/** Stable workflow lane key for this live foreground child. */
|
|
1547
|
+
workflowKey?: string;
|
|
1548
|
+
/** Private control root used to steer a live workflow-owned child. */
|
|
1549
|
+
workflowSteeringDir?: string;
|
|
1541
1550
|
/** Originating parent session; required for public fleet projection. */
|
|
1542
1551
|
sessionId?: string;
|
|
1543
1552
|
mode: SubagentRunMode;
|
|
@@ -1607,6 +1616,8 @@ export interface SubagentState {
|
|
|
1607
1616
|
fleetJobs?: Map<string, AsyncJobState>;
|
|
1608
1617
|
/** Suppress dynamic status widgets while the fleet overlay owns the viewport. */
|
|
1609
1618
|
fleetInspectorOpen?: boolean;
|
|
1619
|
+
/** Temporarily suppress dynamic widgets while Pi compacts the session. */
|
|
1620
|
+
widgetsSuspended?: boolean;
|
|
1610
1621
|
foregroundRuns?: Map<string, ForegroundResumeRun>;
|
|
1611
1622
|
foregroundControls: Map<string, ForegroundRunControl>;
|
|
1612
1623
|
lastForegroundControlId: string | null;
|
|
@@ -1673,6 +1684,10 @@ export interface RunSyncOptions {
|
|
|
1673
1684
|
permissions?: import("../runs/shared/permissions.ts").PermissionConfig;
|
|
1674
1685
|
/** Session id of the direct parent session for permission-system ask forwarding. */
|
|
1675
1686
|
parentSessionId?: string;
|
|
1687
|
+
/** Private prompt-runtime steering transport for workflow-owned foreground children. */
|
|
1688
|
+
steerInboxDir?: string;
|
|
1689
|
+
steerCapabilityPath?: string;
|
|
1690
|
+
steerAckDir?: string;
|
|
1676
1691
|
/** Resolved launch context for this child. */
|
|
1677
1692
|
context?: "fresh" | "fork";
|
|
1678
1693
|
cwd?: string;
|
|
@@ -1780,16 +1795,40 @@ export interface ScheduledRunsConfig {
|
|
|
1780
1795
|
|
|
1781
1796
|
export type FleetViewPlacement = "aboveEditor" | "belowEditor";
|
|
1782
1797
|
|
|
1798
|
+
export const FLEET_KEYBINDING_ACTIONS = [
|
|
1799
|
+
"close",
|
|
1800
|
+
"scrollUp",
|
|
1801
|
+
"scrollDown",
|
|
1802
|
+
"selectUp",
|
|
1803
|
+
"selectDown",
|
|
1804
|
+
"selectFirst",
|
|
1805
|
+
"selectLast",
|
|
1806
|
+
"pageUp",
|
|
1807
|
+
"pageDown",
|
|
1808
|
+
"refresh",
|
|
1809
|
+
"steer",
|
|
1810
|
+
"inspect",
|
|
1811
|
+
"stop",
|
|
1812
|
+
"toggleTools",
|
|
1813
|
+
] as const;
|
|
1814
|
+
|
|
1815
|
+
export type FleetKeybindingAction = typeof FLEET_KEYBINDING_ACTIONS[number];
|
|
1816
|
+
export type FleetKeybindingsConfig = Partial<Record<FleetKeybindingAction, string[]>>;
|
|
1817
|
+
|
|
1783
1818
|
export interface ExtensionConfig {
|
|
1784
1819
|
asyncByDefault?: boolean;
|
|
1785
1820
|
/** Show the Claude Code-style navigable fleet. Defaults to true. */
|
|
1786
1821
|
fleetView?: boolean;
|
|
1787
1822
|
/** Place the persistent FleetView above or below the editor. Defaults to belowEditor. */
|
|
1788
1823
|
fleetViewPlacement?: FleetViewPlacement;
|
|
1824
|
+
/** Local keybindings for the full Fleet inspector. */
|
|
1825
|
+
fleetKeybindings?: FleetKeybindingsConfig;
|
|
1789
1826
|
/** Show the under-editor async runs widget. Defaults to true, including when FleetView is enabled. */
|
|
1790
1827
|
asyncWidget?: boolean;
|
|
1791
1828
|
/** Tool description variant registered for the parent-facing subagent tool. Defaults to full. */
|
|
1792
1829
|
toolDescriptionMode?: ToolDescriptionMode;
|
|
1830
|
+
/** Include legacy append-step and checkpoint controls in the registered tool schema. Defaults to false. */
|
|
1831
|
+
legacyChainControls?: boolean;
|
|
1793
1832
|
/** Inline chat rendering for the subagent tool. Defaults to rich. */
|
|
1794
1833
|
inlineToolDisplay?: InlineToolDisplay;
|
|
1795
1834
|
forceTopLevelAsync?: boolean;
|
|
@@ -1813,7 +1852,7 @@ export interface ExtensionConfig {
|
|
|
1813
1852
|
worktreeSetupHook?: string;
|
|
1814
1853
|
worktreeSetupHookTimeoutMs?: number;
|
|
1815
1854
|
worktreeBaseDir?: string;
|
|
1816
|
-
/** Where to store subagent artifact files. Defaults to "project" (cwd/.pi
|
|
1855
|
+
/** Where to store subagent artifact files. Defaults to "project" (cwd/.pi/subagents). Set to "session" for pi session dir, or "temp" for OS temp. */
|
|
1817
1856
|
artifactDir?: ArtifactDirPreference;
|
|
1818
1857
|
intercomBridge?: IntercomBridgeConfig;
|
|
1819
1858
|
proactiveSkillSubagents?: ProactiveSkillSubagentsConfig | false;
|
|
@@ -1922,7 +1961,7 @@ export const SLASH_SUBAGENT_CANCEL_EVENT = "subagent:slash:cancel";
|
|
|
1922
1961
|
export const POLL_INTERVAL_MS = 250;
|
|
1923
1962
|
export const MAX_WIDGET_JOBS = 4;
|
|
1924
1963
|
export const DEFAULT_SUBAGENT_MAX_DEPTH = 2;
|
|
1925
|
-
export const SUBAGENT_ACTIONS = ["list", "get", "models", "children.list", "create", "update", "delete", "eject", "disable", "enable", "reset", "mission.create", "mission.list", "mission.show", "mission.update", "mission.attach-run", "mission.close", "worktree.discard", "refine", "refine.show", "refine.rollback", "inspector.open", "inspector.status", "inspector.close", "project.open", "project.status", "project.close", "status", "grant-spawn-budget", "interrupt", "resume", "steer", "stop", "append-step", "approve-checkpoint", "reject-checkpoint", "doctor", "watchdog.status", "watchdog.check", "watchdog.configure", "watchdog.recommend-model", "schedule.create", "schedule.list", "schedule.show", "schedule.history", "schedule.pause", "schedule.resume", "schedule.run", "schedule.run-due", "schedule.delete"] as const;
|
|
1964
|
+
export const SUBAGENT_ACTIONS = ["list", "get", "models", "children.list", "guide", "create", "update", "delete", "eject", "disable", "enable", "reset", "mission.create", "mission.list", "mission.show", "mission.update", "mission.resolve-decision", "mission.attach-run", "mission.close", "worktree.discard", "refine", "refine.show", "refine.rollback", "inspector.open", "inspector.status", "inspector.close", "project.open", "project.status", "project.close", "status", "grant-spawn-budget", "interrupt", "resume", "steer", "stop", "append-step", "approve-checkpoint", "reject-checkpoint", "doctor", "watchdog.status", "watchdog.check", "watchdog.configure", "watchdog.recommend-model", "schedule.create", "schedule.list", "schedule.show", "schedule.history", "schedule.pause", "schedule.resume", "schedule.run", "schedule.run-due", "schedule.delete"] as const;
|
|
1926
1965
|
|
|
1927
1966
|
export const DEFAULT_FORK_PREAMBLE =
|
|
1928
1967
|
"You are a delegated subagent running from a fork of the parent session. " +
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export function decodeUtf8Tail(bytes: Buffer): string {
|
|
2
|
+
let start = 0;
|
|
3
|
+
while (start < bytes.length && (bytes[start]! & 0xc0) === 0x80) start += 1;
|
|
4
|
+
return bytes.subarray(start).toString("utf-8");
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function utf8Tail(value: string, maxBytes: number): { text: string; truncated: boolean } {
|
|
8
|
+
const bytes = Buffer.from(value, "utf-8");
|
|
9
|
+
if (bytes.length <= maxBytes) return { text: value, truncated: false };
|
|
10
|
+
return { text: decodeUtf8Tail(bytes.subarray(bytes.length - maxBytes)), truncated: true };
|
|
11
|
+
}
|
package/src/shared/utils.ts
CHANGED
|
@@ -6,6 +6,7 @@ import * as fs from "node:fs";
|
|
|
6
6
|
import * as os from "node:os";
|
|
7
7
|
import * as path from "node:path";
|
|
8
8
|
import type { Message } from "@earendil-works/pi-ai";
|
|
9
|
+
import { previewDisplayText, sanitizeDisplayText, truncateDisplayText } from "./display-text.ts";
|
|
9
10
|
import { formatToolCall } from "./formatters.ts";
|
|
10
11
|
import type { AgentProgress, AsyncStatus, Details, DisplayItem, ErrorInfo, NestedRunSummary, SingleResult, ToolCallSummary, Usage } from "./types.ts";
|
|
11
12
|
|
|
@@ -101,6 +102,23 @@ export function getAgentDir(): string {
|
|
|
101
102
|
|
|
102
103
|
const statusCache = new Map<string, { mtime: number; ctime: number; size: number; ino: number; status: AsyncStatus }>();
|
|
103
104
|
|
|
105
|
+
export function pruneStatusCacheForAsyncRoot(asyncDirRoot: string, runIds: Iterable<string>): number {
|
|
106
|
+
const root = path.resolve(asyncDirRoot);
|
|
107
|
+
const currentStatusPaths = new Set(
|
|
108
|
+
Array.from(runIds, (runId) => path.resolve(root, runId, "status.json")),
|
|
109
|
+
);
|
|
110
|
+
let removed = 0;
|
|
111
|
+
for (const statusPath of statusCache.keys()) {
|
|
112
|
+
const resolved = path.resolve(statusPath);
|
|
113
|
+
const relative = path.relative(root, resolved);
|
|
114
|
+
if (relative && !relative.startsWith("..") && !path.isAbsolute(relative) && !currentStatusPaths.has(resolved)) {
|
|
115
|
+
statusCache.delete(statusPath);
|
|
116
|
+
removed++;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return removed;
|
|
120
|
+
}
|
|
121
|
+
|
|
104
122
|
function getErrorMessage(error: unknown): string {
|
|
105
123
|
return error instanceof Error ? error.message : String(error);
|
|
106
124
|
}
|
|
@@ -127,7 +145,10 @@ export function readStatus(asyncDir: string): AsyncStatus | null {
|
|
|
127
145
|
try {
|
|
128
146
|
stat = fs.statSync(statusPath);
|
|
129
147
|
} catch (error) {
|
|
130
|
-
if (isNotFoundError(error))
|
|
148
|
+
if (isNotFoundError(error)) {
|
|
149
|
+
statusCache.delete(statusPath);
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
131
152
|
throw new Error(`Failed to inspect async status file '${statusPath}': ${getErrorMessage(error)}`, {
|
|
132
153
|
cause: error instanceof Error ? error : undefined,
|
|
133
154
|
});
|
|
@@ -148,7 +169,10 @@ export function readStatus(asyncDir: string): AsyncStatus | null {
|
|
|
148
169
|
try {
|
|
149
170
|
content = fs.readFileSync(statusPath, "utf-8");
|
|
150
171
|
} catch (error) {
|
|
151
|
-
if (isNotFoundError(error))
|
|
172
|
+
if (isNotFoundError(error)) {
|
|
173
|
+
statusCache.delete(statusPath);
|
|
174
|
+
return null;
|
|
175
|
+
}
|
|
152
176
|
throw new Error(`Failed to read async status file '${statusPath}': ${getErrorMessage(error)}`, {
|
|
153
177
|
cause: error instanceof Error ? error : undefined,
|
|
154
178
|
});
|
|
@@ -170,10 +194,6 @@ export function readStatus(asyncDir: string): AsyncStatus | null {
|
|
|
170
194
|
ino: stat.ino,
|
|
171
195
|
status,
|
|
172
196
|
});
|
|
173
|
-
if (statusCache.size > 50) {
|
|
174
|
-
const firstKey = statusCache.keys().next().value;
|
|
175
|
-
if (firstKey) statusCache.delete(firstKey);
|
|
176
|
-
}
|
|
177
197
|
return status;
|
|
178
198
|
}
|
|
179
199
|
|
|
@@ -522,11 +542,8 @@ export function detectSubagentError(messages: Message[]): ErrorInfo {
|
|
|
522
542
|
* Extract a preview of tool arguments for display
|
|
523
543
|
*/
|
|
524
544
|
export function extractToolArgsPreview(args: Record<string, unknown>): string {
|
|
525
|
-
const truncatePreview = (value: string, maxLength: number): string =>
|
|
526
|
-
value.length > maxLength ? `${value.slice(0, maxLength - 3)}...` : value;
|
|
527
|
-
|
|
528
545
|
const stringifyPreviewValue = (value: unknown): string | undefined => {
|
|
529
|
-
if (typeof value === "string" && value.trim().length > 0) return value;
|
|
546
|
+
if (typeof value === "string" && value.trim().length > 0) return sanitizeDisplayText(value);
|
|
530
547
|
if (typeof value === "number" || typeof value === "boolean") return String(value);
|
|
531
548
|
return undefined;
|
|
532
549
|
};
|
|
@@ -539,39 +556,32 @@ export function extractToolArgsPreview(args: Record<string, unknown>): string {
|
|
|
539
556
|
return `${first}${suffix}`;
|
|
540
557
|
};
|
|
541
558
|
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
const
|
|
545
|
-
|
|
546
|
-
return `${server}${args.tool}${toolArgs}`;
|
|
559
|
+
if (typeof args.tool === "string") {
|
|
560
|
+
const server = typeof args.server === "string" ? `${sanitizeDisplayText(args.server)}/` : "";
|
|
561
|
+
const toolArgs = typeof args.args === "string" ? ` ${truncateDisplayText(sanitizeDisplayText(args.args), 40)}` : "";
|
|
562
|
+
return sanitizeDisplayText(`${server}${args.tool}${toolArgs}`);
|
|
547
563
|
}
|
|
548
564
|
|
|
549
565
|
const queriesPreview = previewArray(args.queries);
|
|
550
|
-
if (queriesPreview) return
|
|
551
|
-
if (typeof args.query === "string" && args.query.trim().length > 0) return
|
|
552
|
-
if (typeof args.workflow === "string" && args.workflow.trim().length > 0) return `workflow=${
|
|
566
|
+
if (queriesPreview) return previewDisplayText(queriesPreview, 60);
|
|
567
|
+
if (typeof args.query === "string" && args.query.trim().length > 0) return previewDisplayText(args.query, 60);
|
|
568
|
+
if (typeof args.workflow === "string" && args.workflow.trim().length > 0) return `workflow=${previewDisplayText(args.workflow, 48)}`;
|
|
553
569
|
|
|
554
|
-
if (typeof args.url === "string" && args.url.trim().length > 0) return
|
|
570
|
+
if (typeof args.url === "string" && args.url.trim().length > 0) return previewDisplayText(args.url, 60);
|
|
555
571
|
const urlsPreview = previewArray(args.urls);
|
|
556
|
-
if (urlsPreview) return
|
|
557
|
-
if (typeof args.prompt === "string" && args.prompt.trim().length > 0) return
|
|
558
|
-
|
|
572
|
+
if (urlsPreview) return previewDisplayText(urlsPreview, 60);
|
|
573
|
+
if (typeof args.prompt === "string" && args.prompt.trim().length > 0) return previewDisplayText(args.prompt, 60);
|
|
574
|
+
|
|
559
575
|
const previewKeys = ["command", "path", "file_path", "pattern", "query", "url", "task", "describe", "search"];
|
|
560
576
|
for (const key of previewKeys) {
|
|
561
|
-
if (
|
|
562
|
-
const value = args[key] as string;
|
|
563
|
-
return truncatePreview(value, 60);
|
|
564
|
-
}
|
|
577
|
+
if (typeof args[key] === "string") return previewDisplayText(args[key], 60);
|
|
565
578
|
}
|
|
566
|
-
|
|
567
|
-
// Fallback: show first string value found
|
|
579
|
+
|
|
568
580
|
for (const [key, value] of Object.entries(args)) {
|
|
581
|
+
const displayKey = sanitizeDisplayText(key);
|
|
569
582
|
const arrayPreview = previewArray(value);
|
|
570
|
-
if (arrayPreview) return `${
|
|
571
|
-
if (typeof value === "string" && value.length > 0) {
|
|
572
|
-
const preview = truncatePreview(value, 50);
|
|
573
|
-
return `${key}=${preview}`;
|
|
574
|
-
}
|
|
583
|
+
if (arrayPreview) return `${displayKey}=${previewDisplayText(arrayPreview, 50)}`;
|
|
584
|
+
if (typeof value === "string" && value.length > 0) return `${displayKey}=${previewDisplayText(value, 50)}`;
|
|
575
585
|
}
|
|
576
586
|
return "";
|
|
577
587
|
}
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as path from "node:path";
|
|
3
|
-
import { fileURLToPath } from "node:url";
|
|
4
3
|
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
5
4
|
import { parseFrontmatter } from "../agents/frontmatter.ts";
|
|
6
5
|
import type { SubagentParamsLike } from "../runs/foreground/subagent-executor.ts";
|
|
7
|
-
import {
|
|
6
|
+
import { getPromptDirectories } from "../shared/prompt-resources.ts";
|
|
8
7
|
|
|
9
8
|
interface PromptWorkflow {
|
|
10
9
|
name: string;
|
|
@@ -32,21 +31,9 @@ const RESERVED_COMMAND_NAMES = new Set([
|
|
|
32
31
|
"subagents-models",
|
|
33
32
|
]);
|
|
34
33
|
|
|
35
|
-
function packagePromptsDir(): string {
|
|
36
|
-
return path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "..", "prompts");
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
function promptDirs(cwd: string): string[] {
|
|
40
|
-
return [
|
|
41
|
-
packagePromptsDir(),
|
|
42
|
-
path.join(getAgentDir(), "prompts"),
|
|
43
|
-
path.join(getProjectConfigDir(cwd), "prompts"),
|
|
44
|
-
];
|
|
45
|
-
}
|
|
46
|
-
|
|
47
34
|
function readPromptFiles(cwd: string): string[] {
|
|
48
35
|
const files: string[] = [];
|
|
49
|
-
for (const dir of
|
|
36
|
+
for (const dir of Object.values(getPromptDirectories(cwd))) {
|
|
50
37
|
let entries: fs.Dirent[];
|
|
51
38
|
try {
|
|
52
39
|
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
@@ -4,6 +4,7 @@ import * as path from "node:path";
|
|
|
4
4
|
import { keyText, type ExtensionAPI, type ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
5
5
|
import { Key, matchesKey, truncateToWidth, type Component, type TUI } from "@earendil-works/pi-tui";
|
|
6
6
|
import { BUILTIN_AGENT_NAMES, discoverAgents } from "../agents/agents.ts";
|
|
7
|
+
import { resolveExistingReadPaths } from "../shared/settings.ts";
|
|
7
8
|
import {
|
|
8
9
|
DEFAULT_PROVIDER_MODELS_MAX_AGE_DAYS,
|
|
9
10
|
applySubagentProfile,
|
|
@@ -22,6 +23,7 @@ import { SUBAGENT_FANOUT_CHILD_ENV } from "../runs/shared/pi-args.ts";
|
|
|
22
23
|
import type { SlashSubagentResponse, SlashSubagentUpdate } from "./slash-bridge.ts";
|
|
23
24
|
import { registerPromptWorkflowCommands } from "./prompt-workflows.ts";
|
|
24
25
|
import { openSubagentsAdmin } from "./subagents-admin.ts";
|
|
26
|
+
import { SUBAGENT_GUIDE_TOPICS } from "../extension/subagent-guide.ts";
|
|
25
27
|
import { openSubagentFleet } from "../tui/fleet.ts";
|
|
26
28
|
import {
|
|
27
29
|
applySlashUpdate,
|
|
@@ -40,6 +42,7 @@ import {
|
|
|
40
42
|
SLASH_SUBAGENT_UPDATE_EVENT,
|
|
41
43
|
DIRS,
|
|
42
44
|
type Details,
|
|
45
|
+
type FleetKeybindingsConfig,
|
|
43
46
|
type JsonSchemaObject,
|
|
44
47
|
type SingleResult,
|
|
45
48
|
type SubagentState,
|
|
@@ -628,6 +631,7 @@ function slashRunWorkflowScript(key: string, child: Record<string, unknown>): st
|
|
|
628
631
|
export function registerSlashCommands(
|
|
629
632
|
pi: ExtensionAPI,
|
|
630
633
|
state: SubagentState,
|
|
634
|
+
options: { fleetKeybindings?: FleetKeybindingsConfig } = {},
|
|
631
635
|
): void {
|
|
632
636
|
let fleetOpen = false;
|
|
633
637
|
const showFleet = async (ctx: ExtensionContext) => {
|
|
@@ -642,7 +646,7 @@ export function registerSlashCommands(
|
|
|
642
646
|
}
|
|
643
647
|
fleetOpen = true;
|
|
644
648
|
try {
|
|
645
|
-
await openSubagentFleet(ctx, state, { asyncDirRoot: DIRS.async, resultsDir: DIRS.results });
|
|
649
|
+
await openSubagentFleet(ctx, state, { asyncDirRoot: DIRS.async, resultsDir: DIRS.results, fleetKeybindings: options.fleetKeybindings });
|
|
646
650
|
} finally {
|
|
647
651
|
fleetOpen = false;
|
|
648
652
|
}
|
|
@@ -672,7 +676,8 @@ export function registerSlashCommands(
|
|
|
672
676
|
|
|
673
677
|
let finalTask = task;
|
|
674
678
|
if (inline.reads && Array.isArray(inline.reads) && inline.reads.length > 0) {
|
|
675
|
-
|
|
679
|
+
const existingReads = inline.reads.filter((read) => resolveExistingReadPaths([read], state.baseCwd).length > 0);
|
|
680
|
+
if (existingReads.length > 0) finalTask = `[Read from: ${existingReads.join(", ")}]\n\n${finalTask}`;
|
|
676
681
|
}
|
|
677
682
|
const child: Record<string, unknown> = { agent: agentName, task: finalTask, agentScope: "both" };
|
|
678
683
|
if (inline.output !== undefined) child.output = inline.output;
|
|
@@ -698,6 +703,21 @@ export function registerSlashCommands(
|
|
|
698
703
|
},
|
|
699
704
|
});
|
|
700
705
|
|
|
706
|
+
pi.registerCommand("subagents-guide", {
|
|
707
|
+
description: "Show a packaged subagents guide topic",
|
|
708
|
+
getArgumentCompletions: (prefix) => prefix.includes(" ") ? null : SUBAGENT_GUIDE_TOPICS
|
|
709
|
+
.filter((topic) => topic.startsWith(prefix))
|
|
710
|
+
.map((topic) => ({ value: topic, label: topic })),
|
|
711
|
+
handler: async (args, ctx) => {
|
|
712
|
+
const topic = args.trim();
|
|
713
|
+
if (topic.includes(" ")) {
|
|
714
|
+
ctx.ui.notify("Usage: /subagents-guide [topic]", "error");
|
|
715
|
+
return;
|
|
716
|
+
}
|
|
717
|
+
await runSlashSubagent(pi, ctx, { action: "guide", ...(topic ? { topic } : {}) });
|
|
718
|
+
},
|
|
719
|
+
});
|
|
720
|
+
|
|
701
721
|
pi.registerCommand("subagents-refine", {
|
|
702
722
|
description: "Generate a bounded project-local refinement overlay for one subagent",
|
|
703
723
|
getArgumentCompletions: makeAgentCompletions(state),
|
package/src/tui/fleet-status.ts
CHANGED
|
@@ -144,6 +144,12 @@ function isStaleExtensionContextError(error: unknown): boolean {
|
|
|
144
144
|
|| error.message.includes("Extension context no longer active"));
|
|
145
145
|
}
|
|
146
146
|
|
|
147
|
+
function foregroundDescription(control: { parentWorkflowRunId?: string; workflowKey?: string }, description: string | undefined): string | undefined {
|
|
148
|
+
if (!control.parentWorkflowRunId) return description;
|
|
149
|
+
const workflow = `workflow child: ${control.parentWorkflowRunId}${control.workflowKey ? ` (${control.workflowKey})` : ""}`;
|
|
150
|
+
return description ? `${workflow} · ${description}` : workflow;
|
|
151
|
+
}
|
|
152
|
+
|
|
147
153
|
export function collectFleetStatusEntries(state: SubagentState): FleetStatusEntry[] {
|
|
148
154
|
const entries: FleetStatusEntry[] = [];
|
|
149
155
|
for (const control of state.foregroundControls.values()) {
|
|
@@ -154,7 +160,7 @@ export function collectFleetStatusEntries(state: SubagentState): FleetStatusEntr
|
|
|
154
160
|
key: `foreground-active:${control.runId}:${child.index}`,
|
|
155
161
|
agent: child.agent,
|
|
156
162
|
...(modelThinking ? { modelThinking } : {}),
|
|
157
|
-
description: child.description,
|
|
163
|
+
description: foregroundDescription(control, child.description),
|
|
158
164
|
startedAt: child.startedAt,
|
|
159
165
|
tokens: child.tokens ?? 0,
|
|
160
166
|
state: "running",
|
|
@@ -170,7 +176,7 @@ export function collectFleetStatusEntries(state: SubagentState): FleetStatusEntr
|
|
|
170
176
|
key: `foreground-active:${control.runId}:${control.currentIndex ?? 0}`,
|
|
171
177
|
agent: control.currentAgent ?? control.mode,
|
|
172
178
|
...(modelThinking ? { modelThinking } : {}),
|
|
173
|
-
description: control.description,
|
|
179
|
+
description: foregroundDescription(control, control.description),
|
|
174
180
|
startedAt: control.startedAt,
|
|
175
181
|
tokens: control.tokens ?? 0,
|
|
176
182
|
state: "running",
|
|
@@ -301,26 +307,22 @@ export class SubagentFleetStatus {
|
|
|
301
307
|
refresh(): void {
|
|
302
308
|
const ctx = this.getActiveUiContext();
|
|
303
309
|
if (!ctx) return;
|
|
310
|
+
if (this.state.widgetsSuspended) {
|
|
311
|
+
this.clearWidget();
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
304
314
|
this.entries = collectFleetStatusEntries(this.state);
|
|
305
315
|
this.clampSelection();
|
|
306
316
|
if (this.inspectorOpen || this.state.fleetInspectorOpen) {
|
|
307
317
|
this.lastRenderKey = "";
|
|
308
|
-
|
|
309
|
-
ctx.ui.setWidget(FLEET_STATUS_WIDGET_KEY, undefined);
|
|
310
|
-
this.widgetRegistered = false;
|
|
311
|
-
this.tui = undefined;
|
|
312
|
-
}
|
|
318
|
+
this.clearWidget();
|
|
313
319
|
return;
|
|
314
320
|
}
|
|
315
321
|
if (this.entries.length === 0) {
|
|
316
322
|
this.active = false;
|
|
317
323
|
this.selectedKey = "main";
|
|
318
324
|
this.lastRenderKey = "";
|
|
319
|
-
|
|
320
|
-
ctx.ui.setWidget(FLEET_STATUS_WIDGET_KEY, undefined);
|
|
321
|
-
this.widgetRegistered = false;
|
|
322
|
-
this.tui = undefined;
|
|
323
|
-
}
|
|
325
|
+
this.clearWidget();
|
|
324
326
|
return;
|
|
325
327
|
}
|
|
326
328
|
|
|
@@ -350,6 +352,7 @@ export class SubagentFleetStatus {
|
|
|
350
352
|
}
|
|
351
353
|
|
|
352
354
|
handleKey(data: string): { consume?: boolean; data?: string } | undefined {
|
|
355
|
+
if (this.state.widgetsSuspended) return undefined;
|
|
353
356
|
const ctx = this.getActiveUiContext();
|
|
354
357
|
if (!ctx || this.entries.length === 0 || isKeyRelease(data)) return undefined;
|
|
355
358
|
if (this.inspectorOpen) return undefined;
|
|
@@ -530,6 +533,13 @@ export class SubagentFleetStatus {
|
|
|
530
533
|
}
|
|
531
534
|
}
|
|
532
535
|
|
|
536
|
+
private clearWidget(): void {
|
|
537
|
+
if (!this.widgetRegistered) return;
|
|
538
|
+
this.ui?.setWidget(FLEET_STATUS_WIDGET_KEY, undefined);
|
|
539
|
+
this.widgetRegistered = false;
|
|
540
|
+
this.tui = undefined;
|
|
541
|
+
}
|
|
542
|
+
|
|
533
543
|
private clearUiRegistration(): void {
|
|
534
544
|
if (this.timer) clearInterval(this.timer);
|
|
535
545
|
this.timer = undefined;
|