@pi-unipi/subagents 0.1.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 (49) hide show
  1. package/dist/agent-manager.d.ts +69 -0
  2. package/dist/agent-manager.d.ts.map +1 -0
  3. package/dist/agent-manager.js +240 -0
  4. package/dist/agent-manager.js.map +1 -0
  5. package/dist/agent-runner.d.ts +50 -0
  6. package/dist/agent-runner.d.ts.map +1 -0
  7. package/dist/agent-runner.js +238 -0
  8. package/dist/agent-runner.js.map +1 -0
  9. package/dist/config.d.ts +24 -0
  10. package/dist/config.d.ts.map +1 -0
  11. package/dist/config.js +115 -0
  12. package/dist/config.js.map +1 -0
  13. package/dist/custom-agents.d.ts +14 -0
  14. package/dist/custom-agents.d.ts.map +1 -0
  15. package/dist/custom-agents.js +94 -0
  16. package/dist/custom-agents.js.map +1 -0
  17. package/dist/file-lock.d.ts +42 -0
  18. package/dist/file-lock.d.ts.map +1 -0
  19. package/dist/file-lock.js +91 -0
  20. package/dist/file-lock.js.map +1 -0
  21. package/dist/index.d.ts +9 -0
  22. package/dist/index.d.ts.map +1 -0
  23. package/dist/index.js +270 -0
  24. package/dist/index.js.map +1 -0
  25. package/dist/prompts.d.ts +13 -0
  26. package/dist/prompts.d.ts.map +1 -0
  27. package/dist/prompts.js +31 -0
  28. package/dist/prompts.js.map +1 -0
  29. package/dist/types.d.ts +79 -0
  30. package/dist/types.d.ts.map +1 -0
  31. package/dist/types.js +6 -0
  32. package/dist/types.js.map +1 -0
  33. package/dist/widget.d.ts +22 -0
  34. package/dist/widget.d.ts.map +1 -0
  35. package/dist/widget.js +108 -0
  36. package/dist/widget.js.map +1 -0
  37. package/package.json +30 -0
  38. package/src/agent-manager.ts +302 -0
  39. package/src/agent-runner.ts +306 -0
  40. package/src/config.ts +128 -0
  41. package/src/custom-agents.ts +106 -0
  42. package/src/file-lock.ts +102 -0
  43. package/src/index.ts +323 -0
  44. package/src/prompts.ts +39 -0
  45. package/src/skills/explore/SKILL.md +32 -0
  46. package/src/skills/work/SKILL.md +40 -0
  47. package/src/types.ts +86 -0
  48. package/src/widget.ts +123 -0
  49. package/tsconfig.json +19 -0
package/src/index.ts ADDED
@@ -0,0 +1,323 @@
1
+ /**
2
+ * @pi-unipi/subagents — Extension entry
3
+ *
4
+ * Tools: Agent, get_result
5
+ * ESC propagation: all children abort on parent ESC
6
+ */
7
+
8
+ import { defineTool, type ExtensionAPI, type ExtensionContext } from "@mariozechner/pi-coding-agent";
9
+ import { Text } from "@mariozechner/pi-tui";
10
+ import { Type } from "@sinclair/typebox";
11
+ import { AgentManager } from "./agent-manager.js";
12
+ import { initConfig, saveGlobalConfig } from "./config.js";
13
+ import { type AgentActivity, type AgentRecord, BUILTIN_TYPES } from "./types.js";
14
+ import { AgentWidget } from "./widget.js";
15
+
16
+ /** Format tokens safely. */
17
+ function safeFormatTokens(session: any): string {
18
+ if (!session) return "";
19
+ try {
20
+ const stats = session.getSessionStats();
21
+ const total = stats.tokens?.total ?? 0;
22
+ if (total >= 1_000_000) return `${(total / 1_000_000).toFixed(1)}M`;
23
+ if (total >= 1_000) return `${(total / 1_000).toFixed(1)}k`;
24
+ return `${total}`;
25
+ } catch {
26
+ return "";
27
+ }
28
+ }
29
+
30
+ /** Build result text. */
31
+ function textResult(msg: string, details?: any) {
32
+ return { content: [{ type: "text" as const, text: msg }], details };
33
+ }
34
+
35
+ export default function (pi: ExtensionAPI) {
36
+ // Initialize config
37
+ const config = initConfig(process.cwd());
38
+ if (!config.enabled) return;
39
+
40
+ // Activity tracking for widget
41
+ const agentActivity = new Map<string, AgentActivity>();
42
+
43
+ // Create manager with completion callback
44
+ const manager = new AgentManager(
45
+ (record) => {
46
+ // On complete: clean up activity, emit event
47
+ agentActivity.delete(record.id);
48
+ widget.markFinished(record.id);
49
+ widget.update();
50
+
51
+ pi.events.emit("subagents:completed", {
52
+ id: record.id,
53
+ type: record.type,
54
+ description: record.description,
55
+ status: record.status,
56
+ result: record.result,
57
+ error: record.error,
58
+ });
59
+ },
60
+ config.maxConcurrent,
61
+ (record) => {
62
+ // On start: emit event
63
+ pi.events.emit("subagents:started", {
64
+ id: record.id,
65
+ type: record.type,
66
+ description: record.description,
67
+ });
68
+ },
69
+ );
70
+
71
+ // Create widget
72
+ const widget = new AgentWidget(manager, agentActivity);
73
+
74
+ // ESC propagation: abort all agents on session shutdown
75
+ pi.on("session_shutdown", async () => {
76
+ manager.abortAll();
77
+ manager.dispose();
78
+ });
79
+
80
+ // Wire UI context for widget
81
+ pi.on("tool_execution_start", async (_event, ctx) => {
82
+ widget.setUICtx(ctx.ui);
83
+ widget.update();
84
+ });
85
+
86
+ // Create activity tracker
87
+ function createActivityTracker(maxTurns?: number) {
88
+ const state: AgentActivity = {
89
+ activeTools: new Map(),
90
+ toolUses: 0,
91
+ turnCount: 1,
92
+ maxTurns,
93
+ tokens: "",
94
+ responseText: "",
95
+ };
96
+
97
+ const callbacks = {
98
+ onToolActivity: (activity: { type: "start" | "end"; toolName: string }) => {
99
+ if (activity.type === "start") {
100
+ state.activeTools.set(activity.toolName + "_" + Date.now(), activity.toolName);
101
+ } else {
102
+ for (const [key, name] of state.activeTools) {
103
+ if (name === activity.toolName) {
104
+ state.activeTools.delete(key);
105
+ break;
106
+ }
107
+ }
108
+ state.toolUses++;
109
+ }
110
+ widget.update();
111
+ },
112
+ onTextDelta: (_delta: string, fullText: string) => {
113
+ state.responseText = fullText;
114
+ widget.update();
115
+ },
116
+ onTurnEnd: (turnCount: number) => {
117
+ state.turnCount = turnCount;
118
+ widget.update();
119
+ },
120
+ onSessionCreated: (session: any) => {
121
+ state.session = session;
122
+ state.tokens = safeFormatTokens(session);
123
+ widget.update();
124
+ },
125
+ };
126
+
127
+ return { state, callbacks };
128
+ }
129
+
130
+ // ---- Agent tool ----
131
+
132
+ const builtinTypes = BUILTIN_TYPES.join(", ");
133
+
134
+ pi.registerTool(
135
+ defineTool({
136
+ name: "Agent",
137
+ label: "Agent",
138
+ description: `Launch a sub-agent for parallel work.
139
+
140
+ Available agent types: ${builtinTypes}
141
+ Custom types can be defined in .unipi/config/agents/<name>.md
142
+
143
+ Guidelines:
144
+ - Use "explore" for parallel file reads
145
+ - Use "work" for parallel file writes (transparent locking)
146
+ - Use run_in_background for work you don't need immediately
147
+ - ESC kills all running agents immediately
148
+ - Agents inherit the parent model by default`,
149
+ parameters: Type.Object({
150
+ type: Type.String({
151
+ description: `Agent type: ${builtinTypes}, or custom type from .unipc/config/agents/*.md`,
152
+ }),
153
+ prompt: Type.String({
154
+ description: "The task for the agent to perform.",
155
+ }),
156
+ description: Type.String({
157
+ description: "A short (3-5 word) description of the task.",
158
+ }),
159
+ run_in_background: Type.Optional(
160
+ Type.Boolean({
161
+ description: "Run in background. Returns agent ID immediately.",
162
+ }),
163
+ ),
164
+ max_turns: Type.Optional(
165
+ Type.Number({
166
+ description: "Max agentic turns before stopping.",
167
+ minimum: 1,
168
+ }),
169
+ ),
170
+ }),
171
+
172
+ execute: async (toolCallId, params, signal, onUpdate, ctx) => {
173
+ widget.setUICtx(ctx.ui);
174
+
175
+ const type = params.type as string;
176
+ const prompt = params.prompt as string;
177
+ const description = params.description as string;
178
+ const runInBackground = params.run_in_background as boolean | undefined;
179
+ const maxTurns = params.max_turns as number | undefined;
180
+
181
+ // Create activity tracker
182
+ const { state: bgState, callbacks: bgCallbacks } = createActivityTracker(maxTurns);
183
+
184
+ if (runInBackground) {
185
+ // Background execution
186
+ const id = manager.spawn(pi, ctx, type, prompt, {
187
+ description,
188
+ maxTurns,
189
+ isBackground: true,
190
+ ...bgCallbacks,
191
+ });
192
+
193
+ agentActivity.set(id, bgState);
194
+ widget.ensureTimer();
195
+ widget.update();
196
+
197
+ const record = manager.getRecord(id);
198
+ const isQueued = record?.status === "queued";
199
+
200
+ return textResult(
201
+ `Agent ${isQueued ? "queued" : "started"} in background.\n` +
202
+ `ID: ${id}\n` +
203
+ `Type: ${type}\n` +
204
+ `Description: ${description}\n` +
205
+ (isQueued ? `Position: queued (max ${manager.getMaxConcurrent()} concurrent)\n` : "") +
206
+ `\nYou will be notified when this agent completes.\n` +
207
+ `Use get_result to retrieve full results.`,
208
+ { status: "background", agentId: id },
209
+ );
210
+ }
211
+
212
+ // Foreground execution
213
+ let spinnerFrame = 0;
214
+ const startedAt = Date.now();
215
+ let fgId: string | undefined;
216
+
217
+ const streamUpdate = () => {
218
+ onUpdate?.({
219
+ content: [{ type: "text", text: `${bgState.toolUses} tool uses...` }],
220
+ details: {
221
+ status: "running",
222
+ toolUses: bgState.toolUses,
223
+ tokens: bgState.tokens,
224
+ turnCount: bgState.turnCount,
225
+ maxTurns: bgState.maxTurns,
226
+ durationMs: Date.now() - startedAt,
227
+ activity: bgState.responseText
228
+ ? bgState.responseText.split("\n").pop()?.trim().slice(0, 60)
229
+ : "thinking…",
230
+ spinnerFrame: spinnerFrame % 10,
231
+ },
232
+ });
233
+ };
234
+
235
+ const spinnerInterval = setInterval(() => {
236
+ spinnerFrame++;
237
+ streamUpdate();
238
+ }, 80);
239
+
240
+ streamUpdate();
241
+
242
+ const record = await manager.spawnAndWait(pi, ctx, type, prompt, {
243
+ description,
244
+ maxTurns,
245
+ ...bgCallbacks,
246
+ });
247
+
248
+ clearInterval(spinnerInterval);
249
+
250
+ if (fgId) {
251
+ agentActivity.delete(fgId);
252
+ widget.markFinished(fgId);
253
+ }
254
+
255
+ const tokenText = safeFormatTokens(bgState.session);
256
+ const durationMs = (record.completedAt ?? Date.now()) - record.startedAt;
257
+
258
+ if (record.status === "error") {
259
+ return textResult(`Agent failed: ${record.error}`);
260
+ }
261
+
262
+ return textResult(
263
+ `Agent completed in ${(durationMs / 1000).toFixed(1)}s (${record.toolUses} tool uses${tokenText ? `, ${tokenText} tokens` : ""}).\n\n` +
264
+ (record.result?.trim() || "No output."),
265
+ );
266
+ },
267
+ }),
268
+ );
269
+
270
+ // ---- get_result tool ----
271
+
272
+ pi.registerTool(
273
+ defineTool({
274
+ name: "get_result",
275
+ label: "Get Agent Result",
276
+ description: "Check status and retrieve results from a background agent.",
277
+ parameters: Type.Object({
278
+ agent_id: Type.String({
279
+ description: "The agent ID to check.",
280
+ }),
281
+ wait: Type.Optional(
282
+ Type.Boolean({
283
+ description: "Wait for completion. Default: false.",
284
+ }),
285
+ ),
286
+ }),
287
+ execute: async (_toolCallId, params) => {
288
+ const record = manager.getRecord(params.agent_id as string);
289
+ if (!record) {
290
+ return textResult(`Agent not found: "${params.agent_id}". It may have been cleaned up.`);
291
+ }
292
+
293
+ if (params.wait && record.status === "running" && record.promise) {
294
+ record.resultConsumed = true;
295
+ await record.promise;
296
+ }
297
+
298
+ const duration = record.completedAt
299
+ ? `${((record.completedAt - record.startedAt) / 1000).toFixed(1)}s`
300
+ : "running";
301
+
302
+ let output =
303
+ `Agent: ${record.id}\n` +
304
+ `Type: ${record.type} | Status: ${record.status} | Tool uses: ${record.toolUses} | Duration: ${duration}\n` +
305
+ `Description: ${record.description}\n\n`;
306
+
307
+ if (record.status === "running") {
308
+ output += "Agent is still running. Use wait: true or check back later.";
309
+ } else if (record.status === "error") {
310
+ output += `Error: ${record.error}`;
311
+ } else {
312
+ output += record.result?.trim() || "No output.";
313
+ }
314
+
315
+ if (record.status !== "running" && record.status !== "queued") {
316
+ record.resultConsumed = true;
317
+ }
318
+
319
+ return textResult(output);
320
+ },
321
+ }),
322
+ );
323
+ }
package/src/prompts.ts ADDED
@@ -0,0 +1,39 @@
1
+ /**
2
+ * @pi-unipi/subagents — System prompt builder
3
+ */
4
+
5
+ import type { AgentConfig } from "./types.js";
6
+
7
+ /**
8
+ * Build system prompt for an agent.
9
+ */
10
+ export function buildAgentPrompt(
11
+ config: AgentConfig,
12
+ cwd: string,
13
+ env: { isGitRepo: boolean; branch: string; platform: string },
14
+ parentSystemPrompt: string,
15
+ ): string {
16
+ if (config.promptMode === "append") {
17
+ // Append mode: parent prompt + agent additions
18
+ return [
19
+ parentSystemPrompt,
20
+ "",
21
+ "---",
22
+ "",
23
+ `## Agent Role: ${config.displayName ?? config.name}`,
24
+ config.systemPrompt,
25
+ ].join("\n");
26
+ }
27
+
28
+ // Replace mode: standalone prompt
29
+ return [
30
+ `# ${config.displayName ?? config.name}`,
31
+ "",
32
+ config.systemPrompt,
33
+ "",
34
+ "---",
35
+ "",
36
+ `Working directory: ${cwd}`,
37
+ `Git: ${env.isGitRepo ? `${env.branch} on ${env.platform}` : "not a git repo"}`,
38
+ ].join("\n");
39
+ }
@@ -0,0 +1,32 @@
1
+ ---
2
+ name: explore
3
+ description: "Fast parallel codebase exploration"
4
+ ---
5
+
6
+ # Explore Agent
7
+
8
+ Read-only agent for fast parallel codebase exploration.
9
+
10
+ ## Capabilities
11
+
12
+ - Read files
13
+ - Search with grep, find, ls
14
+ - Run bash commands (read-only)
15
+
16
+ ## Constraints
17
+
18
+ - Cannot write or edit files
19
+ - Cannot modify the codebase
20
+ - Report findings only
21
+
22
+ ## Usage
23
+
24
+ Spawn multiple explore agents to read different parts of the codebase in parallel.
25
+
26
+ ```
27
+ Agent({
28
+ type: "explore",
29
+ prompt: "Find all files related to authentication",
30
+ description: "Find auth files"
31
+ })
32
+ ```
@@ -0,0 +1,40 @@
1
+ ---
2
+ name: work
3
+ description: "Parallel file writes with transparent locking"
4
+ ---
5
+
6
+ # Work Agent
7
+
8
+ Read-write agent for parallel file modifications.
9
+
10
+ ## Capabilities
11
+
12
+ - Read files
13
+ - Write and edit files
14
+ - Run bash commands
15
+ - Search with grep, find, ls
16
+
17
+ ## File Locking
18
+
19
+ When writing a file, the lock is acquired automatically. If another agent holds the lock, your write waits transparently — you won't see errors.
20
+
21
+ - Per-file granularity: locking `src/auth.ts` doesn't block `src/login.ts`
22
+ - Locks release automatically when the write completes
23
+ - On abort, all locks are released
24
+
25
+ ## Constraints
26
+
27
+ - Cannot spawn sub-agents (prevents nesting)
28
+ - Cannot modify other agents' locked files (waits instead)
29
+
30
+ ## Usage
31
+
32
+ Spawn work agents to modify different files in parallel.
33
+
34
+ ```
35
+ Agent({
36
+ type: "work",
37
+ prompt: "Refactor src/auth.ts to use async/await",
38
+ description: "Refactor auth module"
39
+ })
40
+ ```
package/src/types.ts ADDED
@@ -0,0 +1,86 @@
1
+ /**
2
+ * @pi-unipi/subagents — Type definitions
3
+ */
4
+
5
+ import type { ThinkingLevel } from "@mariozechner/pi-agent-core";
6
+ import type { AgentSession } from "@mariozechner/pi-coding-agent";
7
+
8
+ export type { ThinkingLevel };
9
+
10
+ /** Agent type name: built-in or user-defined. */
11
+ export type AgentType = string;
12
+
13
+ /** Built-in agent type names. */
14
+ export const BUILTIN_TYPES = ["explore", "work"] as const;
15
+
16
+ /** Memory scope for persistent agent memory. */
17
+ export type MemoryScope = "user" | "project" | "local";
18
+
19
+ /** Unified agent configuration. */
20
+ export interface AgentConfig {
21
+ name: string;
22
+ displayName?: string;
23
+ description: string;
24
+ builtinToolNames?: string[];
25
+ disallowedTools?: string[];
26
+ extensions: true | string[] | false;
27
+ skills: true | string[] | false;
28
+ model?: string;
29
+ thinking?: ThinkingLevel;
30
+ maxTurns?: number;
31
+ systemPrompt: string;
32
+ promptMode: "replace" | "append";
33
+ inheritContext?: boolean;
34
+ runInBackground?: boolean;
35
+ isolated?: boolean;
36
+ memory?: MemoryScope;
37
+ isDefault?: boolean;
38
+ enabled?: boolean;
39
+ source?: "builtin" | "project" | "global";
40
+ }
41
+
42
+ /** Agent record — tracks a running agent. */
43
+ export interface AgentRecord {
44
+ id: string;
45
+ type: AgentType;
46
+ description: string;
47
+ status: "queued" | "running" | "completed" | "aborted" | "stopped" | "error";
48
+ result?: string;
49
+ error?: string;
50
+ toolUses: number;
51
+ startedAt: number;
52
+ completedAt?: number;
53
+ session?: AgentSession;
54
+ abortController?: AbortController;
55
+ promise?: Promise<string>;
56
+ /** Set when result consumed via get_result — suppresses notification. */
57
+ resultConsumed?: boolean;
58
+ /** Files locked by this agent. */
59
+ lockedFiles: Set<string>;
60
+ }
61
+
62
+ /** File lock entry. */
63
+ export interface FileLockEntry {
64
+ agentId: string;
65
+ filePath: string;
66
+ promise: Promise<void>;
67
+ release: () => void;
68
+ }
69
+
70
+ /** Extension config. */
71
+ export interface SubagentsConfig {
72
+ maxConcurrent: number;
73
+ enabled: boolean;
74
+ types: Record<string, { enabled?: boolean }>;
75
+ }
76
+
77
+ /** Agent activity for widget display. */
78
+ export interface AgentActivity {
79
+ activeTools: Map<string, string>;
80
+ toolUses: number;
81
+ turnCount: number;
82
+ maxTurns?: number;
83
+ tokens: string;
84
+ responseText: string;
85
+ session?: AgentSession;
86
+ }
package/src/widget.ts ADDED
@@ -0,0 +1,123 @@
1
+ /**
2
+ * @pi-unipi/subagents — Live widget
3
+ *
4
+ * Shows running agents above the editor.
5
+ * Adapted from pi-subagents.
6
+ */
7
+
8
+ import type { AgentManager } from "./agent-manager.js";
9
+ import type { AgentActivity } from "./types.js";
10
+
11
+ /** Spinner frames (braille). */
12
+ const SPINNER = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
13
+
14
+ /** Format token count. */
15
+ function formatTokens(n: number): string {
16
+ if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(1)}M`;
17
+ if (n >= 1_000) return `${(n / 1_000).toFixed(1)}k`;
18
+ return `${n}`;
19
+ }
20
+
21
+ /** Format duration. */
22
+ function formatMs(ms: number): string {
23
+ if (ms >= 60_000) return `${(ms / 60_000).toFixed(1)}m`;
24
+ if (ms >= 1_000) return `${(ms / 1_000).toFixed(1)}s`;
25
+ return `${ms}ms`;
26
+ }
27
+
28
+ /** Format turns. */
29
+ function formatTurns(turn: number, max?: number): string {
30
+ return max ? `⟳${turn}≤${max}` : `⟳${turn}`;
31
+ }
32
+
33
+ /** Describe current activity from active tools. */
34
+ function describeActivity(activeTools: Map<string, string>, responseText: string): string {
35
+ if (activeTools.size > 0) {
36
+ const names = [...new Set(activeTools.values())];
37
+ return names.join(", ") + "…";
38
+ }
39
+ if (responseText) {
40
+ const lastLine = responseText.split("\n").pop()?.trim() ?? "";
41
+ if (lastLine.length > 0) return lastLine.slice(0, 60) + (lastLine.length > 60 ? "…" : "");
42
+ }
43
+ return "thinking…";
44
+ }
45
+
46
+ export class AgentWidget {
47
+ private manager: AgentManager;
48
+ private activity: Map<string, AgentActivity>;
49
+ private spinnerFrame = 0;
50
+ private timer?: ReturnType<typeof setInterval>;
51
+ private uiCtx?: any;
52
+
53
+ constructor(manager: AgentManager, activity: Map<string, AgentActivity>) {
54
+ this.manager = manager;
55
+ this.activity = activity;
56
+ }
57
+
58
+ setUICtx(ctx: any) {
59
+ this.uiCtx = ctx;
60
+ }
61
+
62
+ ensureTimer() {
63
+ if (this.timer) return;
64
+ this.timer = setInterval(() => {
65
+ this.spinnerFrame = (this.spinnerFrame + 1) % SPINNER.length;
66
+ this.render();
67
+ }, 80);
68
+ }
69
+
70
+ markFinished(_id: string) {
71
+ // Check if any agents still running
72
+ if (!this.manager.hasRunning()) {
73
+ if (this.timer) {
74
+ clearInterval(this.timer);
75
+ this.timer = undefined;
76
+ }
77
+ }
78
+ }
79
+
80
+ update() {
81
+ this.render();
82
+ }
83
+
84
+ private render() {
85
+ const agents = this.manager.listAgents();
86
+ const running = agents.filter((a) => a.status === "running" || a.status === "queued");
87
+
88
+ if (running.length === 0) {
89
+ // Clear widget
90
+ this.uiCtx?.setIndicator?.("");
91
+ return;
92
+ }
93
+
94
+ const lines: string[] = [];
95
+ const frame = SPINNER[this.spinnerFrame];
96
+
97
+ for (const agent of running) {
98
+ const act = this.activity.get(agent.id);
99
+ const toolCount = agent.toolUses;
100
+ const tokens = act?.tokens ?? "";
101
+ const duration = formatMs(Date.now() - agent.startedAt);
102
+ const activity = act ? describeActivity(act.activeTools, act.responseText) : "starting…";
103
+
104
+ const parts: string[] = [];
105
+ if (act?.turnCount) parts.push(formatTurns(act.turnCount, act.maxTurns));
106
+ if (toolCount > 0) parts.push(`${toolCount} tool uses`);
107
+ if (tokens) parts.push(tokens);
108
+ parts.push(duration);
109
+
110
+ lines.push(
111
+ `${frame} ${agent.type} ${agent.description} · ${parts.join(" · ")}`,
112
+ ` ⎿ ${activity}`,
113
+ );
114
+ }
115
+
116
+ const queued = agents.filter((a) => a.status === "queued").length;
117
+ if (queued > 0) {
118
+ lines.push(` ${queued} queued`);
119
+ }
120
+
121
+ this.uiCtx?.setIndicator?.(lines.join("\n"));
122
+ }
123
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,19 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "Node16",
5
+ "moduleResolution": "Node16",
6
+ "lib": ["ES2022"],
7
+ "outDir": "dist",
8
+ "rootDir": "src",
9
+ "strict": true,
10
+ "esModuleInterop": true,
11
+ "skipLibCheck": true,
12
+ "forceConsistentCasingInFileNames": true,
13
+ "declaration": true,
14
+ "declarationMap": true,
15
+ "sourceMap": true
16
+ },
17
+ "include": ["src/**/*"],
18
+ "exclude": ["node_modules", "dist"]
19
+ }