@hheei/pi-subagents 0.2.3 → 0.3.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.
Files changed (44) hide show
  1. package/README.md +32 -17
  2. package/dist/agent-resolver.d.ts +9 -0
  3. package/dist/agent-resolver.js +278 -51
  4. package/dist/bridge-client.d.ts +5 -0
  5. package/dist/bridge-client.js +6 -0
  6. package/dist/builtin-agents.js +50 -37
  7. package/dist/child-bridge.d.ts +0 -8
  8. package/dist/child-bridge.js +60 -43
  9. package/dist/child-control.d.ts +5 -3
  10. package/dist/child-control.js +176 -35
  11. package/dist/commands.d.ts +1 -4
  12. package/dist/commands.js +1 -52
  13. package/dist/domain.d.ts +12 -12
  14. package/dist/domain.js +2 -1
  15. package/dist/extension.d.ts +5 -3
  16. package/dist/extension.js +42 -44
  17. package/dist/host-adapter.js +1 -1
  18. package/dist/info.d.ts +9 -0
  19. package/dist/info.js +54 -0
  20. package/dist/launch-spec.d.ts +0 -1
  21. package/dist/launch-spec.js +38 -18
  22. package/dist/manager.d.ts +4 -2
  23. package/dist/manager.js +299 -114
  24. package/dist/registry.js +19 -5
  25. package/dist/rpc-events.d.ts +1 -1
  26. package/dist/rpc-events.js +0 -1
  27. package/dist/runtime.js +8 -0
  28. package/dist/session-bootstrap.d.ts +2 -1
  29. package/dist/session-bootstrap.js +22 -14
  30. package/dist/state.d.ts +13 -0
  31. package/dist/state.js +59 -16
  32. package/dist/status.d.ts +14 -0
  33. package/dist/status.js +36 -0
  34. package/dist/tool-activation.d.ts +3 -4
  35. package/dist/tool-activation.js +13 -215
  36. package/dist/tool-presentation.d.ts +17 -0
  37. package/dist/tool-presentation.js +160 -0
  38. package/dist/tools.d.ts +6 -1
  39. package/dist/tools.js +133 -180
  40. package/package.json +6 -6
  41. package/dist/session-fork.d.ts +0 -16
  42. package/dist/session-fork.js +0 -67
  43. package/dist/widget.d.ts +0 -12
  44. package/dist/widget.js +0 -149
@@ -1,67 +0,0 @@
1
- import { readFile, stat } from "node:fs/promises";
2
- import { findSessionFile } from "./session-bootstrap.js";
3
- /** Reads all JSON entries from a session file on disk. */
4
- export async function readSessionJsonlEntries(sessionPath) {
5
- const raw = await readFile(sessionPath, "utf8");
6
- const lines = raw.split("\n").filter((line) => line.trim() !== "");
7
- const entries = [];
8
- for (const line of lines) {
9
- try {
10
- entries.push(JSON.parse(line));
11
- }
12
- catch {
13
- // ignore trailing malformed line
14
- }
15
- }
16
- return entries;
17
- }
18
- /**
19
- * Resolves the source session JSONL file path for on-demand context inspection.
20
- * Rejects 'current' to prevent ambiguous self-references.
21
- */
22
- export async function resolveForkSessionReference(options) {
23
- const { forkFrom, parentSessionPath, registry } = options;
24
- const target = forkFrom.trim();
25
- if (target === "current") {
26
- throw new Error("forkFrom cannot be 'current'. Use 'parent' to reference the parent session, or specify a subagent id like 'agent-1'.");
27
- }
28
- if (target === "parent" || target === "self") {
29
- if (parentSessionPath !== undefined && parentSessionPath.trim() !== "") {
30
- return parentSessionPath;
31
- }
32
- throw new Error("Parent session has no associated session file on disk to reference");
33
- }
34
- if (registry !== undefined) {
35
- const record = await registry.get(target).catch(() => undefined);
36
- if (record !== undefined) {
37
- const subagentSessionPath = record.sessionPath ??
38
- (await findSessionFile(record.launchConfig.sessionDir, record.sessionId));
39
- if (subagentSessionPath !== undefined) {
40
- return subagentSessionPath;
41
- }
42
- throw new Error(`Subagent ${target} has no session file on disk to reference`);
43
- }
44
- if (/^agent-\d+$/u.test(target)) {
45
- throw new Error(`Subagent ${target} was not found in registry`);
46
- }
47
- }
48
- // Treat as explicit session file path
49
- try {
50
- const st = await stat(target);
51
- if (st.isFile())
52
- return target;
53
- }
54
- catch {
55
- // ignore
56
- }
57
- throw new Error(`Referenced session file not found: ${target}`);
58
- }
59
- /** Formats instructions explaining where the referenced session file is and how to inspect it on demand. */
60
- export function formatSessionReferencePrompt(sessionPath) {
61
- return [
62
- "## Referenced Session Context",
63
- "Prior session transcript is available for reference at:",
64
- sessionPath,
65
- "Use tools (such as read or grep) to inspect earlier messages, tool outputs, or context from this JSONL file on demand as needed instead of loading the entire history.",
66
- ].join("\n");
67
- }
package/dist/widget.d.ts DELETED
@@ -1,12 +0,0 @@
1
- import type { ExtensionAPI, ExtensionContext, Theme } from "@earendil-works/pi-coding-agent";
2
- import { type PublicSubagent, type SubagentState } from "./domain.js";
3
- export declare const SUBAGENT_TERMINAL_DISPLAY_DURATION_MS = 15000;
4
- export interface SubagentWidget {
5
- refresh(children: readonly PublicSubagent[]): void;
6
- dispose(): void;
7
- }
8
- export declare function isTerminalState(state: SubagentState): boolean;
9
- export declare function isWidgetVisibleChild(child: PublicSubagent, nowMs?: number): boolean;
10
- export declare function formatElapsed(fromIso: string, nowMs?: number): string;
11
- export declare function renderSubagentWidget(children: readonly PublicSubagent[], width: number, theme: Theme, nowMs?: number): string[];
12
- export declare function createSubagentWidget(pi: ExtensionAPI, context: ExtensionContext, signal: AbortSignal, initial?: readonly PublicSubagent[]): SubagentWidget | undefined;
package/dist/widget.js DELETED
@@ -1,149 +0,0 @@
1
- import { truncateToWidth } from "@earendil-works/pi-tui";
2
- import { registerWidget } from "@hheei/pi-ext-core";
3
- import { toVisualSubagentState, VISUAL_SUBAGENT_GLYPH, VISUAL_SUBAGENT_TONE, } from "./domain.js";
4
- const WIDGET_ID = "@hheei/pi-subagents:status";
5
- const MAX_ROWS = 8;
6
- const ELAPSED_TICK_MS = 1_000;
7
- export const SUBAGENT_TERMINAL_DISPLAY_DURATION_MS = 15_000;
8
- export function isTerminalState(state) {
9
- return state === "done" || state === "blocked" || state === "error";
10
- }
11
- export function isWidgetVisibleChild(child, nowMs = Date.now()) {
12
- const visual = toVisualSubagentState(child.state, child.interrupted);
13
- if (visual === "running") {
14
- return true;
15
- }
16
- const updated = Date.parse(child.updatedAt);
17
- if (!Number.isNaN(updated) && nowMs - updated < SUBAGENT_TERMINAL_DISPLAY_DURATION_MS) {
18
- return true;
19
- }
20
- return false;
21
- }
22
- function visibleChildren(children, nowMs = Date.now()) {
23
- return children.filter((c) => isWidgetVisibleChild(c, nowMs));
24
- }
25
- export function formatElapsed(fromIso, nowMs = Date.now()) {
26
- const from = Date.parse(fromIso);
27
- if (Number.isNaN(from))
28
- return "";
29
- const totalSeconds = Math.max(0, Math.floor((nowMs - from) / 1000));
30
- if (totalSeconds < 60)
31
- return `${totalSeconds}s`;
32
- const hours = Math.floor(totalSeconds / 3600);
33
- const minutes = Math.floor((totalSeconds % 3600) / 60);
34
- const seconds = totalSeconds % 60;
35
- if (hours > 0)
36
- return `${hours}h ${minutes}m`;
37
- return `${minutes}m ${seconds}s`;
38
- }
39
- export function renderSubagentWidget(children, width, theme, nowMs = Date.now()) {
40
- const visible = visibleChildren(children, nowMs);
41
- if (visible.length === 0)
42
- return [];
43
- const heading = `🤖 ${theme.fg("text", "Agents:")}`;
44
- const lines = [truncateToWidth(heading, width, theme.fg("dim", "…"))];
45
- const rows = visible.slice(0, MAX_ROWS);
46
- const sep = theme.fg("dim", " · ");
47
- for (let index = 0; index < rows.length; index++) {
48
- const child = rows[index];
49
- if (child === undefined)
50
- continue;
51
- const visual = toVisualSubagentState(child.state, child.interrupted);
52
- const glyph = VISUAL_SUBAGENT_GLYPH[visual];
53
- const tone = VISUAL_SUBAGENT_TONE[visual];
54
- const glyphStyled = theme.fg(tone, glyph);
55
- const idStyled = theme.fg("dim", `#${child.id}`);
56
- const nameStyled = theme.fg("text", child.displayName ?? child.agent);
57
- const elapsed = formatElapsed(child.createdAt, nowMs);
58
- const timeStyled = theme.fg("dim", elapsed === "" ? "0s" : elapsed);
59
- const stateStyled = theme.fg(tone, visual);
60
- const turnsCount = child.usage?.turns ?? 0;
61
- const turnsLabel = `${turnsCount} turn${turnsCount === 1 ? "" : "s"}`;
62
- const turnsStyled = theme.fg("dim", turnsLabel);
63
- let detailText = "";
64
- if (child.activeTool !== undefined && child.activeTool.trim() !== "") {
65
- detailText = child.activeTool.trim();
66
- }
67
- else if (child.summary !== undefined && child.summary.trim() !== "") {
68
- detailText = child.summary.replace(/\s+/g, " ").trim();
69
- }
70
- const detailStyled = detailText === "" ? "" : `${sep}${theme.fg("dim", detailText)}`;
71
- const rowContent = `${glyphStyled} ${idStyled} ${nameStyled}${sep}${timeStyled}${sep}${stateStyled}${sep}${turnsStyled}${detailStyled}`;
72
- lines.push(truncateToWidth(rowContent, width, theme.fg("dim", "…")));
73
- }
74
- if (visible.length > MAX_ROWS) {
75
- lines.push(truncateToWidth(theme.fg("dim", `+${visible.length - MAX_ROWS} more`), width, theme.fg("dim", "…")));
76
- }
77
- lines.push("");
78
- return lines;
79
- }
80
- export function createSubagentWidget(pi, context, signal, initial = []) {
81
- if (context.mode !== "tui")
82
- return undefined;
83
- let children = initial;
84
- let disposed = false;
85
- let tick;
86
- const hasRows = () => visibleChildren(children).length > 0;
87
- const widget = registerWidget(pi, context, signal, {
88
- id: WIDGET_ID,
89
- placement: "aboveEditor",
90
- visible: hasRows(),
91
- create: (_tui, theme) => ({
92
- render: (width) => renderSubagentWidget(children, width, theme),
93
- invalidate: () => undefined,
94
- dispose: () => undefined,
95
- }),
96
- });
97
- const stopTick = () => {
98
- if (tick === undefined)
99
- return;
100
- clearInterval(tick);
101
- tick = undefined;
102
- };
103
- const syncTick = () => {
104
- if (disposed || !hasRows()) {
105
- stopTick();
106
- return;
107
- }
108
- // 仅在有进行中任务或待淡出任务时维持 tick,纯 idle 时静默
109
- const hasActiveOrExpiring = children.some((child) => isWidgetVisibleChild(child));
110
- if (!hasActiveOrExpiring) {
111
- stopTick();
112
- return;
113
- }
114
- if (tick !== undefined)
115
- return;
116
- tick = setInterval(() => {
117
- if (!hasRows()) {
118
- widget.setVisible(false);
119
- stopTick();
120
- }
121
- else {
122
- widget.requestRender();
123
- }
124
- }, ELAPSED_TICK_MS);
125
- };
126
- syncTick();
127
- signal.addEventListener("abort", stopTick, { once: true });
128
- return {
129
- refresh(next) {
130
- if (disposed)
131
- return;
132
- children = next;
133
- if (!hasRows())
134
- widget.setVisible(false);
135
- else {
136
- widget.setVisible(true);
137
- widget.requestRender();
138
- }
139
- syncTick();
140
- },
141
- dispose() {
142
- if (disposed)
143
- return;
144
- disposed = true;
145
- stopTick();
146
- widget.dispose();
147
- },
148
- };
149
- }