@pi-unipi/subagents 0.1.10 → 0.1.11

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 (2) hide show
  1. package/package.json +1 -1
  2. package/src/index.ts +77 -106
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-unipi/subagents",
3
- "version": "0.1.10",
3
+ "version": "0.1.11",
4
4
  "description": "Subagents for UniPi — parallel execution, file locking, workflow integration",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
package/src/index.ts CHANGED
@@ -5,23 +5,24 @@
5
5
  * ESC propagation: all children abort on parent ESC
6
6
  */
7
7
 
8
- import { defineTool, type ExtensionAPI, type ExtensionContext } from "@mariozechner/pi-coding-agent";
9
- import { Text } from "@mariozechner/pi-tui";
8
+ import { defineTool, type ExtensionAPI } from "@mariozechner/pi-coding-agent";
10
9
  import { Type } from "@sinclair/typebox";
11
- import { emitEvent, MODULES } from "@pi-unipi/core";
12
- import { UNIPI_EVENTS } from "@pi-unipi/core";
10
+ import { existsSync, readdirSync } from "node:fs";
11
+ import { join } from "node:path";
12
+ import { homedir } from "node:os";
13
+ import { emitEvent, MODULES, UNIPI_EVENTS } from "@pi-unipi/core";
14
+ import { AgentManager } from "./agent-manager.js";
15
+ import { initConfig } from "./config.js";
16
+ import { type AgentActivity, BUILTIN_TYPES } from "./types.js";
17
+ import { AgentWidget } from "./widget.js";
13
18
 
14
- // Get info registry from global
19
+ /** Get info registry from global */
15
20
  function getInfoRegistry() {
16
21
  const g = globalThis as any;
17
22
  return g.__unipi_info_registry;
18
23
  }
19
- import { AgentManager } from "./agent-manager.js";
20
- import { initConfig, saveGlobalConfig } from "./config.js";
21
- import { type AgentActivity, type AgentRecord, BUILTIN_TYPES } from "./types.js";
22
- import { AgentWidget } from "./widget.js";
23
24
 
24
- /** Format tokens safely. */
25
+ /** Format tokens safely */
25
26
  function safeFormatTokens(session: any): string {
26
27
  if (!session) return "";
27
28
  try {
@@ -35,7 +36,7 @@ function safeFormatTokens(session: any): string {
35
36
  }
36
37
  }
37
38
 
38
- /** Build result text. */
39
+ /** Build result text */
39
40
  function textResult(msg: string, details?: any) {
40
41
  return { content: [{ type: "text" as const, text: msg }], details };
41
42
  }
@@ -45,17 +46,21 @@ export default function (pi: ExtensionAPI) {
45
46
  const config = initConfig(process.cwd());
46
47
  if (!config.enabled) return;
47
48
 
49
+ // Compute paths at factory time
50
+ const homeDir = homedir();
51
+ const cwd = process.cwd();
52
+ const globalAgentsDir = join(homeDir, ".unipi", "config", "agents");
53
+ const workspaceAgentsDir = join(cwd, ".unipi", "config", "agents");
54
+
48
55
  // Activity tracking for widget
49
56
  const agentActivity = new Map<string, AgentActivity>();
50
57
 
51
58
  // Create manager with completion callback
52
59
  const manager = new AgentManager(
53
60
  (record) => {
54
- // On complete: clean up activity, emit event
55
61
  agentActivity.delete(record.id);
56
62
  widget.markFinished(record.id);
57
63
  widget.update();
58
-
59
64
  pi.events.emit("subagents:completed", {
60
65
  id: record.id,
61
66
  type: record.type,
@@ -67,7 +72,6 @@ export default function (pi: ExtensionAPI) {
67
72
  },
68
73
  config.maxConcurrent,
69
74
  (record) => {
70
- // On start: emit event
71
75
  pi.events.emit("subagents:started", {
72
76
  id: record.id,
73
77
  type: record.type,
@@ -79,103 +83,78 @@ export default function (pi: ExtensionAPI) {
79
83
  // Create widget
80
84
  const widget = new AgentWidget(manager, agentActivity);
81
85
 
82
- // Session start: notify agent about config paths
83
- pi.on("session_start", async (_event, ctx) => {
84
- const homedir = require("os").homedir();
85
- const globalConfig = `${homedir}/.unipi/config/subagents.json`;
86
- const globalAgents = `${homedir}/.unipi/config/agents/`;
87
- const workspaceConfig = `${ctx.cwd}/.unipi/config/subagents.json`;
88
- const workspaceAgents = `${ctx.cwd}/.unipi/config/agents/`;
89
-
90
- // Register info group
91
- const registry = getInfoRegistry();
92
- if (registry) {
93
- registry.registerGroup({
94
- id: "subagents",
95
- name: "Subagents",
96
- icon: "🤖",
97
- priority: 80,
98
- config: {
99
- showByDefault: true,
100
- stats: [
101
- { id: "maxConcurrent", label: "Max Concurrent", show: true },
102
- { id: "activeCount", label: "Active Agents", show: true },
103
- { id: "enabled", label: "Enabled", show: true },
104
- { id: "types", label: "Available Types", show: true },
105
- ],
106
- },
107
- dataProvider: async () => {
108
- // Get available agent types
109
- const types = config.types || {};
110
- const builtinTypes = ["explore", "work"];
111
-
112
- // Check for custom agent types in filesystem
113
- const customTypes: string[] = [];
86
+ // Register info group at factory time (not session_start)
87
+ const registry = getInfoRegistry();
88
+ if (registry) {
89
+ registry.registerGroup({
90
+ id: "subagents",
91
+ name: "Subagents",
92
+ icon: "🤖",
93
+ priority: 80,
94
+ config: {
95
+ showByDefault: true,
96
+ stats: [
97
+ { id: "maxConcurrent", label: "Max Concurrent", show: true },
98
+ { id: "activeCount", label: "Active Agents", show: true },
99
+ { id: "enabled", label: "Enabled", show: true },
100
+ { id: "types", label: "Available Types", show: true },
101
+ ],
102
+ },
103
+ dataProvider: async () => {
104
+ const types = config.types || {};
105
+ const builtinTypes = ["explore", "work"];
106
+
107
+ // Scan for custom agent types
108
+ const customTypes: string[] = [];
109
+ for (const dir of [globalAgentsDir, workspaceAgentsDir]) {
114
110
  try {
115
- const fs = require("fs");
116
- const path = require("path");
117
-
118
- // Check global agents directory
119
- if (fs.existsSync(globalAgents)) {
120
- const files = fs.readdirSync(globalAgents);
121
- for (const file of files) {
122
- if (file.endsWith(".md")) {
111
+ if (existsSync(dir)) {
112
+ for (const file of readdirSync(dir)) {
113
+ if (file.endsWith(".md") && !customTypes.includes(file.replace(".md", ""))) {
123
114
  customTypes.push(file.replace(".md", ""));
124
115
  }
125
116
  }
126
117
  }
127
-
128
- // Check workspace agents directory
129
- if (fs.existsSync(workspaceAgents)) {
130
- const files = fs.readdirSync(workspaceAgents);
131
- for (const file of files) {
132
- if (file.endsWith(".md")) {
133
- const name = file.replace(".md", "");
134
- if (!customTypes.includes(name)) {
135
- customTypes.push(name);
136
- }
137
- }
138
- }
139
- }
140
- } catch {
141
- // Ignore errors
142
- }
143
-
144
- // Build available types list
145
- const allTypes = [...new Set([...builtinTypes, ...Object.keys(types), ...customTypes])];
146
- const typeList = allTypes.map(t => {
147
- const isEnabled = types[t]?.enabled !== false;
148
- const isBuiltin = builtinTypes.includes(t);
149
- const scope = customTypes.includes(t) ? "project" : "global";
150
- return `${t}(${scope})${isEnabled ? "" : " [disabled]"}`;
151
- }).join(", ");
152
-
153
- // Get active agents count
154
- const activeAgents = manager.listAgents().filter(a => a.status === "running").length;
155
-
156
- return {
157
- maxConcurrent: { value: String(manager.getMaxConcurrent()) },
158
- activeCount: { value: String(activeAgents) },
159
- enabled: { value: config.enabled ? "yes" : "no" },
160
- types: {
161
- value: allTypes.length > 0 ? allTypes[0] : "none",
162
- detail: allTypes.length > 1 ? typeList : undefined,
163
- },
164
- };
165
- },
166
- });
167
- }
118
+ } catch { /* ignore */ }
119
+ }
120
+
121
+ const allTypes = [...new Set([...builtinTypes, ...Object.keys(types), ...customTypes])];
122
+ const typeList = allTypes.map(t => {
123
+ const isEnabled = types[t]?.enabled !== false;
124
+ const isBuiltin = builtinTypes.includes(t);
125
+ const scope = customTypes.includes(t) ? "project" : "global";
126
+ return `${t}(${scope})${isEnabled ? "" : " [disabled]"}`;
127
+ }).join(", ");
128
+
129
+ const activeAgents = manager.listAgents().filter(a => a.status === "running").length;
130
+
131
+ return {
132
+ maxConcurrent: { value: String(manager.getMaxConcurrent()) },
133
+ activeCount: { value: String(activeAgents) },
134
+ enabled: { value: config.enabled ? "yes" : "no" },
135
+ types: {
136
+ value: allTypes.length > 0 ? allTypes[0] : "none",
137
+ detail: allTypes.length > 1 ? typeList : undefined,
138
+ },
139
+ };
140
+ },
141
+ });
142
+ }
143
+
144
+ // Session start: emit MODULE_READY
145
+ pi.on("session_start", async (_event, ctx) => {
146
+ const globalConfig = `${homeDir}/.unipi/config/subagents.json`;
147
+ const workspaceConfig = `${cwd}/.unipi/config/subagents.json`;
168
148
 
169
149
  ctx.ui.notify(
170
150
  `UniPi Subagents config:\n` +
171
151
  `• Global: ${globalConfig}\n` +
172
- `• Global agents: ${globalAgents}\n` +
152
+ `• Global agents: ${globalAgentsDir}\n` +
173
153
  `• Workspace: ${workspaceConfig}\n` +
174
- `• Workspace agents: ${workspaceAgents}`,
154
+ `• Workspace agents: ${workspaceAgentsDir}`,
175
155
  "info",
176
156
  );
177
157
 
178
- // Emit module ready event
179
158
  emitEvent(pi, UNIPI_EVENTS.MODULE_READY, {
180
159
  name: MODULES.SUBAGENTS || "subagents",
181
160
  version: "0.1.8",
@@ -305,11 +284,9 @@ Guidelines:
305
284
  const modelInput = params.model as string | undefined;
306
285
  const thinkingLevel = params.thinking as any | undefined;
307
286
 
308
- // Create activity tracker
309
287
  const { state: bgState, callbacks: bgCallbacks } = createActivityTracker(maxTurns);
310
288
 
311
289
  if (runInBackground) {
312
- // Background execution
313
290
  const id = manager.spawn(pi, ctx, type, prompt, {
314
291
  description,
315
292
  maxTurns,
@@ -342,7 +319,6 @@ Guidelines:
342
319
  // Foreground execution
343
320
  let spinnerFrame = 0;
344
321
  const startedAt = Date.now();
345
- let fgId: string | undefined;
346
322
 
347
323
  const streamUpdate = () => {
348
324
  onUpdate?.({
@@ -382,11 +358,6 @@ Guidelines:
382
358
 
383
359
  clearInterval(spinnerInterval);
384
360
 
385
- if (fgId) {
386
- agentActivity.delete(fgId);
387
- widget.markFinished(fgId);
388
- }
389
-
390
361
  const tokenText = safeFormatTokens(bgState.session);
391
362
  const durationMs = (record.completedAt ?? Date.now()) - record.startedAt;
392
363