@ryan_nookpi/pi-extension-subagent 0.3.0 → 0.3.1

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/commands.ts CHANGED
@@ -42,6 +42,7 @@ import {
42
42
  } from "./group-pending.js";
43
43
  import { enqueueSubagentInvocation } from "./invocation-queue.js";
44
44
  import { appendDisplayTaskUpdate, getSessionFileSize } from "./persisted-session.js";
45
+ import { SUBAGENT_COMMANDS, type SubagentCommandName } from "./registration-manifest.js";
45
46
  import { readSessionReplayItems, SubagentSessionReplayOverlay } from "./replay.js";
46
47
  import { invokeWithAutoRetry, MAX_SUBAGENT_AUTO_RETRIES } from "./retry.js";
47
48
  import { getLatestRun, removeRun, trimCommandRunHistory } from "./run-utils.js";
@@ -890,7 +891,18 @@ function finalizeHumanOnlyCompletion(
890
891
  store.globalLiveRuns.delete(runId);
891
892
  }
892
893
 
893
- export function registerAll(pi: ExtensionAPI, store: SubagentStore): void {
894
+ export type SubagentCommandDefinition = Parameters<ExtensionAPI["registerCommand"]>[1];
895
+
896
+ export interface SubagentRegistrations {
897
+ commands: ReadonlyMap<SubagentCommandName, SubagentCommandDefinition>;
898
+ }
899
+
900
+ export function registerAll(pi: ExtensionAPI, store: SubagentStore): SubagentRegistrations {
901
+ const commandDefinitions = new Map<SubagentCommandName, SubagentCommandDefinition>();
902
+ const defineCommand = (name: SubagentCommandName, definition: SubagentCommandDefinition): void => {
903
+ commandDefinitions.set(name, definition);
904
+ };
905
+
894
906
  pi.registerTool({
895
907
  name: "list-agents",
896
908
  label: "List Agents",
@@ -958,8 +970,7 @@ export function registerAll(pi: ExtensionAPI, store: SubagentStore): void {
958
970
  });
959
971
 
960
972
  const subCommand = {
961
- description:
962
- "Run a subagent in a dedicated sub-session: /sub:isolate <agent|alias> <task>, /sub:isolate <runId> <task>, /sub:isolate <task> (uses configured defaultAgent)",
973
+ description: SUBAGENT_COMMANDS["sub:isolate"],
963
974
  getArgumentCompletions: (argumentPrefix: string) => {
964
975
  const trimmedStart = argumentPrefix.trimStart();
965
976
  if (trimmedStart.includes(" ")) return null;
@@ -1574,10 +1585,10 @@ export function registerAll(pi: ExtensionAPI, store: SubagentStore): void {
1574
1585
  },
1575
1586
  };
1576
1587
 
1577
- pi.registerCommand("sub:isolate", subCommand);
1588
+ defineCommand("sub:isolate", subCommand);
1578
1589
 
1579
- pi.registerCommand("sub:main", {
1580
- description: "Run a subagent with main-session context inheritance: /sub:main <agent|alias> <task>",
1590
+ defineCommand("sub:main", {
1591
+ description: SUBAGENT_COMMANDS["sub:main"],
1581
1592
  getArgumentCompletions: subCommand.getArgumentCompletions,
1582
1593
  handler: async (args, ctx) => {
1583
1594
  captureSwitchSession(store, ctx);
@@ -1586,8 +1597,8 @@ export function registerAll(pi: ExtensionAPI, store: SubagentStore): void {
1586
1597
  },
1587
1598
  });
1588
1599
 
1589
- pi.registerCommand("subagents", {
1590
- description: "List available subagents and offer the starter pack when none are configured",
1600
+ defineCommand("subagents", {
1601
+ description: SUBAGENT_COMMANDS.subagents,
1591
1602
  handler: async (_args, ctx) => {
1592
1603
  captureSwitchSession(store, ctx);
1593
1604
  const starterPack = await offerStarterPackIfEmpty(ctx);
@@ -1619,8 +1630,8 @@ export function registerAll(pi: ExtensionAPI, store: SubagentStore): void {
1619
1630
  },
1620
1631
  });
1621
1632
 
1622
- pi.registerCommand("sub:peek", {
1623
- description: "Show the latest response from a subagent in an overlay: /sub:peek [runId]",
1633
+ defineCommand("sub:peek", {
1634
+ description: SUBAGENT_COMMANDS["sub:peek"],
1624
1635
  getArgumentCompletions: (argumentPrefix) => {
1625
1636
  const trimmedStart = argumentPrefix.trimStart();
1626
1637
  if (trimmedStart.includes(" ")) return null;
@@ -1643,8 +1654,8 @@ export function registerAll(pi: ExtensionAPI, store: SubagentStore): void {
1643
1654
  },
1644
1655
  });
1645
1656
 
1646
- pi.registerCommand("sub:open", {
1647
- description: "Open a subagent session replay overlay: /sub:open [runId]",
1657
+ defineCommand("sub:open", {
1658
+ description: SUBAGENT_COMMANDS["sub:open"],
1648
1659
  getArgumentCompletions: (argumentPrefix) => {
1649
1660
  const trimmedStart = argumentPrefix.trimStart();
1650
1661
  if (trimmedStart.includes(" ")) return null;
@@ -1735,8 +1746,8 @@ export function registerAll(pi: ExtensionAPI, store: SubagentStore): void {
1735
1746
  },
1736
1747
  });
1737
1748
 
1738
- pi.registerCommand("sub:history", {
1739
- description: "Show all subagent run history (including removed) in an overlay: /sub:history",
1749
+ defineCommand("sub:history", {
1750
+ description: SUBAGENT_COMMANDS["sub:history"],
1740
1751
  handler: async (_args, ctx) => {
1741
1752
  captureSwitchSession(store, ctx);
1742
1753
 
@@ -1787,8 +1798,8 @@ export function registerAll(pi: ExtensionAPI, store: SubagentStore): void {
1787
1798
  },
1788
1799
  });
1789
1800
 
1790
- pi.registerCommand("sub:rm", {
1791
- description: "Remove one /sub job entry (aborts it if running): /sub:rm [runId]",
1801
+ defineCommand("sub:rm", {
1802
+ description: SUBAGENT_COMMANDS["sub:rm"],
1792
1803
  handler: async (args, ctx) => {
1793
1804
  captureSwitchSession(store, ctx);
1794
1805
  const raw = (args ?? "").trim();
@@ -1871,8 +1882,8 @@ export function registerAll(pi: ExtensionAPI, store: SubagentStore): void {
1871
1882
  ctx.ui.notify(`Cleared ${removed} finished subagent job(s).`, "info");
1872
1883
  };
1873
1884
 
1874
- pi.registerCommand("sub:clear", {
1875
- description: "Clear /sub job widget entries. /sub:clear (finished only) or /sub:clear all",
1885
+ defineCommand("sub:clear", {
1886
+ description: SUBAGENT_COMMANDS["sub:clear"],
1876
1887
  handler: async (args, ctx) => {
1877
1888
  await handleSubClear(args, ctx);
1878
1889
  },
@@ -1947,23 +1958,16 @@ export function registerAll(pi: ExtensionAPI, store: SubagentStore): void {
1947
1958
  ctx.ui.notify("Usage: /sub:abort [runId|all]", "info");
1948
1959
  };
1949
1960
 
1950
- pi.registerCommand("sub:abort", {
1951
- description: "Abort running subagent job(s). /sub:abort [runId|all]",
1961
+ defineCommand("sub:abort", {
1962
+ description: SUBAGENT_COMMANDS["sub:abort"],
1952
1963
  handler: async (args, ctx) => {
1953
1964
  captureSwitchSession(store, ctx);
1954
1965
  await handleSubAbort(args, ctx);
1955
1966
  },
1956
1967
  });
1957
1968
 
1958
- // Only actual keyboard shortcuts appear in the /hotkeys Extensions section.
1959
- // NOTE: plain ">" is a real keybinding and hijacks editor text input,
1960
- // so the hidden subagent prefix is documented via footer/status hints instead.
1961
- pi.registerShortcut(">>" as any, {
1962
- description: "Run subagent task",
1963
- handler: async () => {
1964
- // Documentation-only entry.
1965
- },
1966
- });
1969
+ // Text-prefix shortcuts are registered synchronously in index.ts for
1970
+ // /hotkeys visibility. Their actual routing remains in the input handlers below.
1967
1971
 
1968
1972
  pi.on("input", async (event, ctx) => {
1969
1973
  if (event.source === "extension") {
@@ -2073,13 +2077,6 @@ export function registerAll(pi: ExtensionAPI, store: SubagentStore): void {
2073
2077
  });
2074
2078
 
2075
2079
  // #<runId> shortcut: resume a subagent run (e.g. #42 keep going)
2076
- pi.registerShortcut("#<runId>" as any, {
2077
- description: "Resume subagent run: #<runId> <task>",
2078
- handler: async () => {
2079
- // Documentation-only entry.
2080
- },
2081
- });
2082
-
2083
2080
  pi.on("input", async (event, ctx) => {
2084
2081
  if (event.source === "extension") {
2085
2082
  return { action: "continue" as const };
@@ -2126,20 +2123,6 @@ export function registerAll(pi: ExtensionAPI, store: SubagentStore): void {
2126
2123
  });
2127
2124
 
2128
2125
  // << shortcut: abort running jobs or clear finished jobs
2129
- pi.registerShortcut("<<" as any, {
2130
- description: "Abort or clear subagent runs",
2131
- handler: async () => {
2132
- // Documentation-only entry.
2133
- },
2134
- });
2135
-
2136
- pi.registerShortcut("<<<" as any, {
2137
- description: "Clear finished subagent jobs (= /sub:clear). <<< all to clear all",
2138
- handler: async () => {
2139
- // Documentation-only entry.
2140
- },
2141
- });
2142
-
2143
2126
  pi.on("input", async (event, ctx) => {
2144
2127
  if (event.source === "extension") {
2145
2128
  return { action: "continue" as const };
@@ -2226,6 +2209,15 @@ export function registerAll(pi: ExtensionAPI, store: SubagentStore): void {
2226
2209
  ctx.ui.notify(parts.join(", ") || "Nothing to do.", parts.length ? (aborted ? "warning" : "info") : "info");
2227
2210
  return { action: "handled" as const };
2228
2211
  });
2212
+
2213
+ const missingCommands = (Object.keys(SUBAGENT_COMMANDS) as SubagentCommandName[]).filter(
2214
+ (name) => !commandDefinitions.has(name),
2215
+ );
2216
+ if (missingCommands.length > 0) {
2217
+ throw new Error(`Missing subagent command definitions: ${missingCommands.join(", ")}`);
2218
+ }
2219
+
2220
+ return { commands: commandDefinitions };
2229
2221
  }
2230
2222
 
2231
2223
  // ── onTerminalInput hack: auto-redirect <>7 to /sub:peek 7 ───────────
package/index.ts CHANGED
@@ -29,10 +29,12 @@
29
29
 
30
30
  import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
31
31
  import { HANG_CHECK_INTERVAL_MS } from "./constants.js";
32
+ import { SUBAGENT_COMMANDS, SUBAGENT_SHORTCUTS, type SubagentCommandName } from "./registration-manifest.js";
32
33
 
33
34
  interface SubagentCore {
34
35
  store: import("./store.js").SubagentStore;
35
36
  commands: typeof import("./commands.js");
37
+ registrations: import("./commands.js").SubagentRegistrations;
36
38
  escalation: typeof import("./escalation.js");
37
39
  lifecycle: typeof import("./lifecycle.js");
38
40
  }
@@ -52,10 +54,13 @@ export default function (pi: ExtensionAPI) {
52
54
  import("./store.js"),
53
55
  ]);
54
56
  const store = storeMod.createStore();
55
- commands.registerAll(pi, store);
56
- core = { store, commands, escalation, lifecycle };
57
+ const registrations = commands.registerAll(pi, store);
58
+ core = { store, commands, registrations, escalation, lifecycle };
57
59
  return core;
58
- })();
60
+ })().catch((error) => {
61
+ corePromise = null;
62
+ throw error;
63
+ });
59
64
  return corePromise;
60
65
  };
61
66
 
@@ -69,6 +74,39 @@ export default function (pi: ExtensionAPI) {
69
74
  return chain;
70
75
  };
71
76
 
77
+ const getCommandDefinition = async (name: SubagentCommandName) => {
78
+ const loaded = await loadCore();
79
+ await chain;
80
+ const definition = loaded.registrations.commands.get(name);
81
+ if (!definition) throw new Error(`Subagent command definition not found: ${name}`);
82
+ return definition;
83
+ };
84
+
85
+ // Register lightweight proxies synchronously so Pi's initial autocomplete
86
+ // and command registry contain every subagent command before lazy core load.
87
+ for (const [name, description] of Object.entries(SUBAGENT_COMMANDS) as Array<[SubagentCommandName, string]>) {
88
+ pi.registerCommand(name, {
89
+ description,
90
+ getArgumentCompletions: async (prefix) => {
91
+ const definition = await getCommandDefinition(name);
92
+ return definition.getArgumentCompletions?.(prefix) ?? null;
93
+ },
94
+ handler: async (args, ctx) => {
95
+ const definition = await getCommandDefinition(name);
96
+ return definition.handler(args, ctx);
97
+ },
98
+ });
99
+ }
100
+
101
+ // These entries document text-prefix shortcuts in /hotkeys. Actual routing
102
+ // remains in commands.ts input handlers after the lazy core is loaded.
103
+ for (const [shortcut, description] of Object.entries(SUBAGENT_SHORTCUTS)) {
104
+ pi.registerShortcut(shortcut as never, {
105
+ description,
106
+ handler: async () => {},
107
+ });
108
+ }
109
+
72
110
  // Start loading in the background without blocking extension boot.
73
111
  // setTimeout keeps the load out of the boot path's microtask drains.
74
112
  setTimeout(() => {
package/package.json CHANGED
@@ -1,98 +1,99 @@
1
1
  {
2
- "name": "@ryan_nookpi/pi-extension-subagent",
3
- "version": "0.3.0",
4
- "description": "Asynchronous subagent delegation for pi with run, batch, chain, and continuation workflows.",
5
- "license": "MIT",
6
- "repository": {
7
- "type": "git",
8
- "url": "https://github.com/Jonghakseo/pi-extension.git",
9
- "directory": "packages/subagent"
10
- },
11
- "bugs": {
12
- "url": "https://github.com/Jonghakseo/pi-extension/issues"
13
- },
14
- "homepage": "https://github.com/Jonghakseo/pi-extension/tree/main/packages/subagent#readme",
15
- "type": "module",
16
- "keywords": [
17
- "pi-package",
18
- "subagent",
19
- "delegation"
20
- ],
21
- "files": [
22
- "above-widget.ts",
23
- "agents.ts",
24
- "claude-args.ts",
25
- "claude-sdk-runner.ts",
26
- "claude-sdk-stream-adapter.ts",
27
- "claude-sidecar-writer.ts",
28
- "claude-stream-parser.ts",
29
- "cli.ts",
30
- "commands.ts",
31
- "config.ts",
32
- "constants.ts",
33
- "context-limits.ts",
34
- "display-task.ts",
35
- "escalation.ts",
36
- "format.ts",
37
- "group-pending.ts",
38
- "index.ts",
39
- "lifecycle.ts",
40
- "invocation-queue.ts",
41
- "live-preview.ts",
42
- "persisted-session.ts",
43
- "replay.ts",
44
- "retry.ts",
45
- "run-utils.ts",
46
- "runner.ts",
47
- "session.ts",
48
- "starter-pack.ts",
49
- "store.ts",
50
- "tool-execute.ts",
51
- "tool-render.ts",
52
- "types.ts",
53
- "widget.ts",
54
- "utils/agent-utils.ts",
55
- "utils/short-label.ts",
56
- "utils/string-utils.ts",
57
- "utils/format-utils.ts",
58
- "utils/time-utils.ts",
59
- "seeds",
60
- "README.md"
61
- ],
62
- "pi": {
63
- "extensions": [
64
- "./index.ts"
65
- ]
66
- },
67
- "dependencies": {
68
- "@anthropic-ai/claude-agent-sdk": "^0.2.98",
69
- "yaml": "^2.9.0"
70
- },
71
- "peerDependencies": {
72
- "@earendil-works/pi-ai": "*",
73
- "@earendil-works/pi-agent-core": "*",
74
- "@earendil-works/pi-coding-agent": "*",
75
- "@earendil-works/pi-tui": "*",
76
- "typebox": "*"
77
- },
78
- "peerDependenciesMeta": {
79
- "@earendil-works/pi-ai": {
80
- "optional": true
81
- },
82
- "@earendil-works/pi-agent-core": {
83
- "optional": true
84
- },
85
- "@earendil-works/pi-coding-agent": {
86
- "optional": true
87
- },
88
- "@earendil-works/pi-tui": {
89
- "optional": true
90
- },
91
- "typebox": {
92
- "optional": true
93
- }
94
- },
95
- "publishConfig": {
96
- "access": "public"
97
- }
98
- }
2
+ "name": "@ryan_nookpi/pi-extension-subagent",
3
+ "version": "0.3.1",
4
+ "description": "Asynchronous subagent delegation for pi with run, batch, chain, and continuation workflows.",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/Jonghakseo/pi-extension.git",
9
+ "directory": "packages/subagent"
10
+ },
11
+ "bugs": {
12
+ "url": "https://github.com/Jonghakseo/pi-extension/issues"
13
+ },
14
+ "homepage": "https://github.com/Jonghakseo/pi-extension/tree/main/packages/subagent#readme",
15
+ "type": "module",
16
+ "keywords": [
17
+ "pi-package",
18
+ "subagent",
19
+ "delegation"
20
+ ],
21
+ "files": [
22
+ "above-widget.ts",
23
+ "agents.ts",
24
+ "claude-args.ts",
25
+ "claude-sdk-runner.ts",
26
+ "claude-sdk-stream-adapter.ts",
27
+ "claude-sidecar-writer.ts",
28
+ "claude-stream-parser.ts",
29
+ "cli.ts",
30
+ "commands.ts",
31
+ "config.ts",
32
+ "constants.ts",
33
+ "context-limits.ts",
34
+ "display-task.ts",
35
+ "escalation.ts",
36
+ "format.ts",
37
+ "group-pending.ts",
38
+ "index.ts",
39
+ "lifecycle.ts",
40
+ "invocation-queue.ts",
41
+ "live-preview.ts",
42
+ "persisted-session.ts",
43
+ "replay.ts",
44
+ "registration-manifest.ts",
45
+ "retry.ts",
46
+ "run-utils.ts",
47
+ "runner.ts",
48
+ "session.ts",
49
+ "starter-pack.ts",
50
+ "store.ts",
51
+ "tool-execute.ts",
52
+ "tool-render.ts",
53
+ "types.ts",
54
+ "widget.ts",
55
+ "utils/agent-utils.ts",
56
+ "utils/short-label.ts",
57
+ "utils/string-utils.ts",
58
+ "utils/format-utils.ts",
59
+ "utils/time-utils.ts",
60
+ "seeds",
61
+ "README.md"
62
+ ],
63
+ "pi": {
64
+ "extensions": [
65
+ "./index.ts"
66
+ ]
67
+ },
68
+ "dependencies": {
69
+ "@anthropic-ai/claude-agent-sdk": "^0.2.98",
70
+ "yaml": "^2.9.0"
71
+ },
72
+ "peerDependencies": {
73
+ "@earendil-works/pi-ai": "*",
74
+ "@earendil-works/pi-agent-core": "*",
75
+ "@earendil-works/pi-coding-agent": "*",
76
+ "@earendil-works/pi-tui": "*",
77
+ "typebox": "*"
78
+ },
79
+ "peerDependenciesMeta": {
80
+ "@earendil-works/pi-ai": {
81
+ "optional": true
82
+ },
83
+ "@earendil-works/pi-agent-core": {
84
+ "optional": true
85
+ },
86
+ "@earendil-works/pi-coding-agent": {
87
+ "optional": true
88
+ },
89
+ "@earendil-works/pi-tui": {
90
+ "optional": true
91
+ },
92
+ "typebox": {
93
+ "optional": true
94
+ }
95
+ },
96
+ "publishConfig": {
97
+ "access": "public"
98
+ }
99
+ }
@@ -0,0 +1,23 @@
1
+ export const SUBAGENT_COMMANDS = {
2
+ "sub:isolate":
3
+ "Run a subagent in a dedicated sub-session: /sub:isolate <agent|alias> <task>, /sub:isolate <runId> <task>, /sub:isolate <task> (uses configured defaultAgent)",
4
+ "sub:main": "Run a subagent with main-session context inheritance: /sub:main <agent|alias> <task>",
5
+ subagents: "List available subagents and offer the starter pack when none are configured",
6
+ "sub:peek": "Show the latest response from a subagent in an overlay: /sub:peek [runId]",
7
+ "sub:open": "Open a subagent session replay overlay: /sub:open [runId]",
8
+ "sub:history": "Show all subagent run history (including removed) in an overlay: /sub:history",
9
+ "sub:rm": "Remove one /sub job entry (aborts it if running): /sub:rm [runId]",
10
+ "sub:clear": "Clear /sub job widget entries. /sub:clear (finished only) or /sub:clear all",
11
+ "sub:abort": "Abort running subagent job(s). /sub:abort [runId|all]",
12
+ } as const;
13
+
14
+ export type SubagentCommandName = keyof typeof SUBAGENT_COMMANDS;
15
+
16
+ export const SUBAGENT_SHORTCUTS = {
17
+ ">>": "Run subagent task",
18
+ "#<runId>": "Resume subagent run: #<runId> <task>",
19
+ "<<": "Abort or clear subagent runs",
20
+ "<<<": "Clear finished subagent jobs (= /sub:clear). <<< all to clear all",
21
+ } as const;
22
+
23
+ export type SubagentShortcutName = keyof typeof SUBAGENT_SHORTCUTS;