pi-subagents-lite 0.4.0 → 1.0.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/src/menus.ts CHANGED
@@ -453,8 +453,8 @@ async function handleAgentBriefing(ctx: ExtensionCommandContext): Promise<void>
453
453
  lines.push(config.description);
454
454
  lines.push("");
455
455
 
456
- if (config.builtinToolNames) {
457
- lines.push(`**Tools:** ${config.builtinToolNames.join(", ")}`);
456
+ if (config.registeredTools) {
457
+ lines.push(`**Tools:** ${config.registeredTools.join(", ")}`);
458
458
  }
459
459
  if (config.model) {
460
460
  lines.push(`**Default model:** ${config.model}`);
@@ -533,7 +533,7 @@ export async function showConcurrencySettingsMenu(
533
533
  items.push(`Default concurrency limit · ${__config.concurrency.default}`);
534
534
  actions.push(async () => {
535
535
  await promptConcurrencyInput(
536
- ctx, "Default concurrency limit", __config.concurrency.default,
536
+ ctx, "Default limit", __config.concurrency.default,
537
537
  (value) => { __config.concurrency.default = value; },
538
538
  );
539
539
  });
@@ -839,13 +839,13 @@ async function showAgentTypes(ctx: ExtensionCommandContext): Promise<void> {
839
839
  for (const name of types) {
840
840
  const cfg = getAgentConfig(name);
841
841
  if (!cfg) continue;
842
- const disabled = cfg.enabled === false ? " [DISABLED]" : "";
842
+ const hidden = cfg.hidden === true ? " [HIDDEN]" : "";
843
843
  const model = cfg.model ? ` Model: ${cfg.model}` : "";
844
- const tools = cfg.builtinToolNames
845
- ? ` Tools: ${cfg.builtinToolNames.join(", ")}`
844
+ const tools = cfg.registeredTools
845
+ ? ` Tools: ${cfg.registeredTools.join(", ")}`
846
846
  : " Tools: all built-in tools";
847
847
  const source = cfg.source ? ` Source: ${cfg.source}` : "";
848
- lines.push(` ${name}${disabled}`);
848
+ lines.push(` ${name}${hidden}`);
849
849
  lines.push(` ${cfg.description}`);
850
850
  if (model) lines.push(model);
851
851
  lines.push(tools);
@@ -1,9 +1,6 @@
1
1
  /**
2
2
  * model-selector.ts — TUI model selection dialog.
3
3
  *
4
- * Ported from subagent-lazy/model-selector.ts verbatim.
5
- * Used by the /agents menu for model selection.
6
- *
7
4
  * Reuses the same building blocks as pi's ModelSelectorComponent but without
8
5
  * the SettingsManager dependency — no side effects, just callbacks.
9
6
  */
@@ -18,10 +15,7 @@ import {
18
15
  Text,
19
16
  } from "@earendil-works/pi-tui";
20
17
  import { DynamicBorder } from "@earendil-works/pi-coding-agent";
21
-
22
- // Theme type from ctx.ui.custom() callback — avoid deep import that may not resolve
23
- // in jiti extension loader. The constructor receives the theme instance directly.
24
- type Theme = any;
18
+ import type { Theme } from "./ui/agent-widget.js";
25
19
 
26
20
  /* ------------------------------------------------------------------ */
27
21
  /* Types */
@@ -1,12 +1,9 @@
1
1
  /**
2
2
  * output-file.ts — Human-readable output logging for agent transcripts.
3
3
  *
4
- * Forked from upstream pi-subagents. Key modifications:
5
- * - Rewrote from JSONL to human-readable format
6
- * - Path changed to /tmp/pi-agent-outputs/<agentId>.log (no CID-encoded nesting)
7
- * - Directory created with 0o700 permissions
8
- * - Append-only, human-readable, supports `tail -f`
9
- * - Lines: [USER], [TOOL], [ASSISTANT], [DONE] with ISO timestamps
4
+ * Path: /tmp/pi-agent-outputs/<agentId>.log
5
+ * Append-only, human-readable, supports `tail -f`.
6
+ * Lines: [USER], [TOOL], [ASSISTANT], [DONE] with ISO timestamps.
10
7
  */
11
8
 
12
9
  import { appendFileSync, mkdirSync, writeFileSync } from "node:fs";
package/src/prompts.ts CHANGED
@@ -7,9 +7,8 @@
7
7
 
8
8
  import type { AgentConfig, EnvInfo } from "./types.js";
9
9
  import type { SkillMeta } from "./skill-loader.js";
10
- export type { SkillMeta };
11
10
 
12
- /** Extra sections to inject into the system prompt (skills only — no memoryBlock). */
11
+ /** Extra sections to inject into the system prompt (skills). */
13
12
  export interface PromptExtras {
14
13
  /** Preloaded skill contents to inject (full content). */
15
14
  skillBlocks?: { name: string; content: string }[];
@@ -45,7 +44,7 @@ export function buildAgentPrompt(
45
44
  envLines.push(`Platform: ${env.platform}`);
46
45
  const envBlock = envLines.join("\n");
47
46
 
48
- // Build optional extras suffix (skills only — no memoryBlock)
47
+ // Build optional extras suffix (skills)
49
48
  const extraSections: string[] = [];
50
49
 
51
50
  // Skill metadata whitelist (like Pi's available_skills format)
@@ -17,10 +17,7 @@ import {
17
17
  } from "@earendil-works/pi-tui";
18
18
  import { DynamicBorder } from "@earendil-works/pi-coding-agent";
19
19
  import { type LifetimeUsage, formatTokens } from "./usage.js";
20
- import { formatMs } from "./ui/agent-widget.js";
21
-
22
- // Theme type from ctx.ui.custom() callback
23
- type Theme = any;
20
+ import { formatMs, type Theme } from "./ui/agent-widget.js";
24
21
 
25
22
  /* ------------------------------------------------------------------ */
26
23
  /* Types */
@@ -16,8 +16,6 @@
16
16
  * SKILL.md is a skill — we don't descend into it (Pi: skills don't nest).
17
17
  *
18
18
  * Symlinks are rejected for security (deviation from Pi, which follows them).
19
- *
20
- * Changed from upstream: imports from ./utils.js instead of ./memory.js.
21
19
  */
22
20
 
23
21
  import type { Dirent } from "node:fs";
@@ -10,7 +10,7 @@ import type { ExtensionContext, ToolCallEvent } from "@earendil-works/pi-coding-
10
10
  import type { AgentRecord } from "./types.js";
11
11
  import type { SpawnOptions as AgentManagerSpawnOptions } from "./agent-manager.js";
12
12
  import type { AgentActivity } from "./ui/agent-widget.js";
13
- import { resolveType, getAgentConfig } from "./agent-types.js";
13
+ import { resolveType, getAgentConfig, discoverNewAgents } from "./agent-types.js";
14
14
  import { resolveModel } from "./model-precedence.js";
15
15
  import { addUsage, getLifetimeTotal, getSessionContextPercent, type LifetimeUsage } from "./usage.js";
16
16
 
@@ -173,7 +173,12 @@ export async function executeAgentTool(
173
173
  ctx: ExtensionContext,
174
174
  ): Promise<any> {
175
175
  const type = (params.agent as string) || "general-purpose";
176
- const resolvedType = resolveType(type);
176
+ let resolvedType = resolveType(type);
177
+ if (!resolvedType) {
178
+ // Not found in registry — try scanning filesystem for agents added during the session
179
+ await discoverNewAgents();
180
+ resolvedType = resolveType(type);
181
+ }
177
182
  if (!resolvedType) {
178
183
  return errorResult(`Unknown agent type: ${type}`);
179
184
  }
@@ -181,7 +186,6 @@ export async function executeAgentTool(
181
186
  const prompt = params.prompt as string;
182
187
  const description = params.description as string;
183
188
  const runInBackground = params.run_in_background as boolean | undefined;
184
- const isolated = params.isolated as boolean | undefined;
185
189
  const maxTurns = params.max_turns as number | undefined ?? getAgentConfig(resolvedType)?.maxTurns;
186
190
  const modelStr = params.model as string | undefined;
187
191
  const model = findModelInRegistry(modelStr, ctx.modelRegistry, ctx.model);
@@ -201,7 +205,6 @@ export async function executeAgentTool(
201
205
  description,
202
206
  model,
203
207
  maxTurns,
204
- isolated,
205
208
  thinkingLevel,
206
209
  modelKey,
207
210
  invocation: modelName ? { modelName } : undefined,
@@ -234,10 +237,7 @@ async function executeSpawnBackground(
234
237
  widget?.ensureTimer();
235
238
  widget?.update();
236
239
 
237
- const record = manager.getRecord(agentId);
238
- if (!record) {
239
- return errorResult("Failed to create agent");
240
- }
240
+ const record = manager.getRecord(agentId)!;
241
241
  const details: Record<string, unknown> = { type: resolvedType, description: spawnOptions.description };
242
242
  const suffix = `A notification will arrive when done - User asks you not to poll or duplicate the delegated work.\n\nAgent ID: ${agentId}`;
243
243
  const label = record.status === "queued" ? "Agent queued" : "Agent running";
@@ -327,11 +327,6 @@ export async function toolCallListener(
327
327
  }
328
328
  }
329
329
 
330
- // Inject isolated from agent config if not explicitly passed
331
- if (input.isolated === undefined && agentConfig?.isolated !== undefined) {
332
- input.isolated = agentConfig.isolated;
333
- }
334
-
335
330
  // Inject thinking from agent config if not explicitly passed
336
331
  if (input.thinking === undefined && agentConfig?.thinking !== undefined) {
337
332
  input.thinking = agentConfig.thinking;
package/src/types.ts CHANGED
@@ -1,11 +1,5 @@
1
1
  /**
2
- * types.ts — Type definitions for the subagent system.
3
- *
4
- * Trimmed from upstream: removed ScheduledSubagent, ScheduleStoreData,
5
- * IsolationMode, MemoryScope, JoinMode.
6
- * From AgentConfig: removed memory, isolation, inheritContext, runInBackground.
7
- * From AgentRecord: removed groupId, joinMode, worktree, worktreeResult.
8
- * From AgentInvocation: removed inheritContext, isolation.
2
+ * Type definitions for the subagent system.
9
3
  */
10
4
 
11
5
  import type { AgentSession } from "@earendil-works/pi-coding-agent";
@@ -22,11 +16,21 @@ export interface AgentConfig {
22
16
  name: string;
23
17
  displayName?: string;
24
18
  description: string;
25
- builtinToolNames?: string[];
26
- /** Tool denylist — these tools are removed even if `builtinToolNames` or extensions include them. */
27
- disallowedTools?: string[];
28
- /** true = inherit all, string[] = only listed, false = none */
19
+ /** Tools to register with the session (controls availability, not LLM visibility). */
20
+ registeredTools?: string[];
21
+ /**
22
+ * Controls which tool schemas the LLM sees. Can reference built-in tools
23
+ * and extension tools. true = all, string[] = listed, false = none.
24
+ * Supports ext/* syntax to include all tools from an extension.
25
+ * Mutually exclusive with excludeTools.
26
+ */
27
+ tools?: true | string[] | false;
28
+ /** Tool blacklist — all tools except these are visible. Mutually exclusive with tools (when tools is string[]). */
29
+ excludeTools?: string[];
30
+ /** true = inherit all, string[] = only listed, false = none. Mutually exclusive with excludeExtensions. */
29
31
  extensions: true | string[] | false;
32
+ /** Extension blacklist — all extensions except these load. Mutually exclusive with extensions (when extensions is string[]). */
33
+ excludeExtensions?: string[];
30
34
  /** Whitelist of allowed skills (metadata only in system prompt). true = all, string[] = listed, false = none */
31
35
  skills: true | string[] | false;
32
36
  /** Skills to preload with full content into system prompt. string[] = listed, false/undefined = none */
@@ -35,14 +39,13 @@ export interface AgentConfig {
35
39
  thinking?: ThinkingLevel;
36
40
  maxTurns?: number;
37
41
  systemPrompt: string;
38
- /** Default for spawn: no extension tools. undefined = caller decides. */
39
- isolated?: boolean;
42
+
40
43
  /** true = this is an embedded default agent (informational) */
41
44
  isDefault?: boolean;
42
- /** false = agent is hidden from the registry */
43
- enabled?: boolean;
45
+ /** true = agent is hidden from the schema enum but can still be called by name. */
46
+ hidden?: boolean;
44
47
  /** Where this agent was loaded from */
45
- source?: "default" | "project" | "global";
48
+ source?: "project" | "global";
46
49
  }
47
50
 
48
51
  export interface AgentRecord {
@@ -87,7 +90,6 @@ export interface AgentInvocation {
87
90
  modelName?: string;
88
91
  thinking?: ThinkingLevel;
89
92
  maxTurns?: number;
90
- isolated?: boolean;
91
93
  runInBackground?: boolean;
92
94
  }
93
95
 
@@ -109,8 +111,4 @@ export interface CompactionInfo {
109
111
  tokensBefore: number;
110
112
  }
111
113
 
112
- /** Parsed "provider/model-id" key. */
113
- export interface ModelKey {
114
- provider: string;
115
- modelId: string;
116
- }
114
+
@@ -1,9 +1,5 @@
1
1
  /**
2
2
  * agent-widget.ts — Persistent widget showing running/completed agents above the editor.
3
- *
4
- * Ported from upstream pi-subagents.
5
- * Import paths use relative imports within our extension.
6
- * addUsage/getLifetimeTotal/getSessionContextPercent imported from ../usage.js.
7
3
  */
8
4
 
9
5
  import { truncateToWidth } from "@earendil-works/pi-tui";
@@ -62,9 +58,10 @@ const TOOL_DISPLAY: Record<string, string> = {
62
58
 
63
59
  // ---- Types ----
64
60
 
65
- type Theme = {
61
+ export type Theme = {
66
62
  fg(color: string, text: string): string;
67
63
  bold(text: string): string;
64
+ italic?: (text: string) => string;
68
65
  };
69
66
 
70
67
  export type UICtx = {
package/src/utils.ts CHANGED
@@ -1,9 +1,8 @@
1
1
  /**
2
- * utils.ts — Security helpers: safe file access, name validation + general utilities.
2
+ * utils.ts — Security helpers and general utilities.
3
3
  *
4
- * Security helpers extracted from upstream memory.ts — pure implementations copied verbatim.
5
- * General utilities (parseModelKey, findModelInRegistry) moved here so both agent-runner
6
- * and tool-execution can use them without circular dependencies.
4
+ * Security helpers (isUnsafeName, isSymlink, safeReadFile) protect against
5
+ * path traversal and symlink attacks in agent/skill name resolution.
7
6
  */
8
7
 
9
8
  import { lstatSync, readFileSync } from "node:fs";
@@ -35,7 +34,7 @@ export function isSymlink(filePath: string): boolean {
35
34
  */
36
35
  export function safeReadFile(filePath: string): string | undefined {
37
36
  try {
38
- if (lstatSync(filePath).isSymbolicLink()) return undefined;
37
+ if (isSymlink(filePath)) return undefined;
39
38
  return readFileSync(filePath, "utf-8");
40
39
  } catch {
41
40
  return undefined;