@tintinweb/pi-subagents 0.7.2 → 0.8.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/CHANGELOG.md CHANGED
@@ -5,7 +5,29 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
- ## [Unreleased]
8
+ ## [0.8.0] - 2026-05-26
9
+
10
+ > **⚠️ Breaking: peer dependencies moved from `@mariozechner/pi-*` to `@earendil-works/pi-*`.** The upstream Pi runtime relocated npm scopes on 2026-05-07; the `@mariozechner/pi-*` packages are deprecated. This release pins `@earendil-works/pi-{ai,coding-agent,tui}` at `>=0.74.0`. Hosts on the old scope must update their pi installation first (`pi update --self` handles the rename automatically) before installing this version.
11
+ >
12
+ > **Note on Node:** this release is tested against `@earendil-works/pi-coding-agent@latest` (currently `0.75.x`), which requires Node `>=22.19.0` because its bundled `undici` calls Node 22+ APIs. CI runs on Node 22. The peer range (`>=0.74.0`) technically also matches the upstream `legacy-node20` line (`0.74.x`, Node 20 compatible) and this extension contains no Node 22+ API calls of its own, but the legacy line is not exercised in CI — consumers pinning it do so at their own risk.
13
+
14
+ ### Changed
15
+ - **Peer deps migrated from `@mariozechner/pi-*` to `@earendil-works/pi-*`** ([#76](https://github.com/tintinweb/pi-subagents/issues/76) — thanks [@SEHANTA](https://github.com/SEHANTA) for the report). On **2026-05-07** the upstream Pi runtime moved npm scopes — `@mariozechner/pi-coding-agent@0.73.1` was the final publish (now deprecated on npm), and `@earendil-works/pi-coding-agent@0.74.0` shipped 30 minutes later from the same monorepo (same author, same code). `peerDependencies` now target `@earendil-works/pi-{ai,coding-agent,tui}` at `>=0.74.0`, and all `src/**` and `test/**` imports are renamed to the new scope — pure rename, no API changes. Consumers pinning the new scope no longer hit the peer-dep conflict warnings reported in [#76](https://github.com/tintinweb/pi-subagents/issues/76).
16
+ - **`ThinkingLevel` now imported from `@earendil-works/pi-ai` instead of `…/pi-agent-core`.** `src/types.ts` previously reached past the public API into `pi-agent-core` (an internal package), which only resolved because npm flat-hoisted it as a transitive of `pi-coding-agent` — under pnpm or strict-resolver setups the import failed (`TS2307: Cannot find module '@mariozechner/pi-agent-core'`). `pi-ai` re-exports `ThinkingLevel` from its public surface (`export * from "./types.ts"`), so the import goes through the documented entry point and no extra peer dep is needed.
17
+
18
+ ### Fixed
19
+ - **`.pi/subagent-schedules/` is no longer created in every working directory.** `ScheduleStore`'s constructor previously ran `mkdirSync` unconditionally, so any session with scheduling enabled left an empty `.pi/subagent-schedules/` dir behind even when nothing was ever scheduled. Directory creation is now lazy — deferred to a new private `ensureDir()` invoked at the top of `withLock`, so the dir (and its `<sessionId>.json`) appear only when a job is actually persisted. Additionally, `update`/`remove` now short-circuit on an unknown id (in-memory `jobs.has(id)` check) before taking the lock, so no-op mutations never touch disk. Read-only use (`list`/`get`/`hasName`) and constructing the store never create the dir. Pre-existing leftover dirs are not cleaned up — remove them manually.
20
+
21
+ ## [0.7.3] - 2026-05-14
22
+
23
+ ### Added
24
+ - **`<active_agent name="…"/>` tag prepended to every child system prompt** ([#73](https://github.com/tintinweb/pi-subagents/pull/73) — thanks [@chris-lasher](https://github.com/chris-lasher)). `buildAgentPrompt` now emits `<active_agent name="${config.name}"/>` as the first line of the assembled prompt in both `replace` and `append` modes, before the env block. Downstream extensions (e.g. permission/policy systems) can parse it from inside the child session to resolve per-agent policy. The tag uses the agent's `config.name` verbatim — no escaping or normalization — and does not couple this extension to any specific downstream consumer; ignoring it is harmless.
25
+
26
+ ### Changed
27
+ - **Subagent sessions now get a stable, type-derived name with an id suffix for parallel spawns** ([#51](https://github.com/tintinweb/pi-subagents/pull/51) — thanks [@forcepushdev](https://github.com/forcepushdev)). `runAgent` calls `session.setSessionName(agentConfig?.name ?? type)`, and when the manager assigns an `agentId` (always, in production), the name is suffixed with an 8-char slice — e.g. `Explore#a1b2c3d4` — so concurrent spawns of the same agent type are distinguishable in the overlay instead of all collapsing onto the same bare name. Direct `runAgent` callers without an `agentId` (e.g. tests) get the bare name.
28
+
29
+ ### Fixed
30
+ - **Cross-extension spawn RPC now accepts a string `options.model`** ([#59](https://github.com/tintinweb/pi-subagents/pull/59), fixes [#60](https://github.com/tintinweb/pi-subagents/issues/60)). Cross-extension callers (e.g. `@tintinweb/pi-tasks@>=0.4.3`'s `TaskExecute`) naturally forward `model` as a serializable `"provider/modelId"` string. Previously the spawn handler passed strings straight through to `runAgent()`, which expects a `Model` object — the spawned agent then crashed with `No API key found for undefined`. The handler now resolves strings via the same `resolveModel(ctx.modelRegistry)` path the scheduler uses; `Model` objects pass through unchanged. Unresolved strings surface the human-readable `Model not found: "…"` error instead of the auth-lookup crash. Thanks @any-victor.
9
31
 
10
32
  ## [0.7.2] - 2026-05-12
11
33
 
package/README.md CHANGED
@@ -406,6 +406,8 @@ pi.events.emit("subagents:rpc:spawn", {
406
406
  });
407
407
  ```
408
408
 
409
+ `options.model` accepts either a `Model` object (e.g. `ctx.model`) or a `"provider/modelId"` string — strings are resolved against `ctx.modelRegistry` at the RPC boundary, so cross-extension callers can forward serializable values without losing auth context.
410
+
409
411
  ### Stop
410
412
 
411
413
  Stop a running agent by ID:
@@ -5,8 +5,8 @@
5
5
  * Excess agents are queued and auto-started as running agents complete.
6
6
  * Foreground agents bypass the queue (they block the parent anyway).
7
7
  */
8
- import type { Model } from "@mariozechner/pi-ai";
9
- import type { AgentSession, ExtensionAPI, ExtensionContext } from "@mariozechner/pi-coding-agent";
8
+ import type { Model } from "@earendil-works/pi-ai";
9
+ import type { AgentSession, ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
10
10
  import { type ToolActivity } from "./agent-runner.js";
11
11
  import type { AgentInvocation, AgentRecord, IsolationMode, SubagentType, ThinkingLevel } from "./types.js";
12
12
  export type OnAgentComplete = (record: AgentRecord) => void;
@@ -107,6 +107,7 @@ export class AgentManager {
107
107
  const detach = () => { detachParentSignal?.(); detachParentSignal = undefined; };
108
108
  const promise = runAgent(ctx, type, prompt, {
109
109
  pi,
110
+ agentId: id,
110
111
  model: options.model,
111
112
  maxTurns: options.maxTurns,
112
113
  isolated: options.isolated,
@@ -1,9 +1,9 @@
1
1
  /**
2
2
  * agent-runner.ts — Core execution engine: creates sessions, runs agents, collects results.
3
3
  */
4
- import type { Model } from "@mariozechner/pi-ai";
5
- import type { ExtensionContext } from "@mariozechner/pi-coding-agent";
6
- import { type AgentSession, type ExtensionAPI } from "@mariozechner/pi-coding-agent";
4
+ import type { Model } from "@earendil-works/pi-ai";
5
+ import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
6
+ import { type AgentSession, type ExtensionAPI } from "@earendil-works/pi-coding-agent";
7
7
  import type { SubagentType, ThinkingLevel } from "./types.js";
8
8
  /** Normalize max turns. undefined or 0 = unlimited, otherwise minimum 1. */
9
9
  export declare function normalizeMaxTurns(n: number | undefined): number | undefined;
@@ -23,6 +23,8 @@ export interface ToolActivity {
23
23
  export interface RunOptions {
24
24
  /** ExtensionAPI instance — used for pi.exec() instead of execSync. */
25
25
  pi: ExtensionAPI;
26
+ /** Manager-assigned id; suffixes session name to disambiguate parallel spawns (e.g. `Explore#a1b2c3d4`). */
27
+ agentId?: string;
26
28
  model?: Model<any>;
27
29
  maxTurns?: number;
28
30
  signal?: AbortSignal;
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * agent-runner.ts — Core execution engine: creates sessions, runs agents, collects results.
3
3
  */
4
- import { createAgentSession, DefaultResourceLoader, getAgentDir, SessionManager, SettingsManager, } from "@mariozechner/pi-coding-agent";
4
+ import { createAgentSession, DefaultResourceLoader, getAgentDir, SessionManager, SettingsManager, } from "@earendil-works/pi-coding-agent";
5
5
  import { getAgentConfig, getConfig, getMemoryToolNames, getReadOnlyMemoryToolNames, getToolNamesForType } from "./agent-types.js";
6
6
  import { buildParentContext, extractText } from "./context.js";
7
7
  import { DEFAULT_AGENTS } from "./default-agents.js";
@@ -187,6 +187,8 @@ export async function runAgent(ctx, type, prompt, options) {
187
187
  sessionOpts.thinkingLevel = thinkingLevel;
188
188
  }
189
189
  const { session } = await createAgentSession(sessionOpts);
190
+ const baseSessionName = agentConfig?.name ?? type;
191
+ session.setSessionName(options.agentId ? `${baseSessionName}#${options.agentId.slice(0, 8)}` : baseSessionName);
190
192
  // Build disallowed tools set from agent config
191
193
  const disallowedSet = agentConfig?.disallowedTools
192
194
  ? new Set(agentConfig.disallowedTools)
package/dist/context.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * context.ts — Extract parent conversation context for subagent inheritance.
3
3
  */
4
- import type { ExtensionContext } from "@mariozechner/pi-coding-agent";
4
+ import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
5
5
  /** Extract text from a message content block array. */
6
6
  export declare function extractText(content: unknown[]): string;
7
7
  /**
@@ -8,6 +8,7 @@
8
8
  * success → { success: true, data?: T }
9
9
  * error → { success: false, error: string }
10
10
  */
11
+ import { resolveModel } from "./model-resolver.js";
11
12
  /** RPC protocol version — bumped when the envelope or method contracts change. */
12
13
  export const PROTOCOL_VERSION = 2;
13
14
  /**
@@ -44,7 +45,28 @@ export function registerRpcHandlers(deps) {
44
45
  const ctx = getCtx();
45
46
  if (!ctx)
46
47
  throw new Error("No active session");
47
- return { id: manager.spawn(pi, ctx, type, prompt, options ?? {}) };
48
+ // Cross-extension RPC callers (e.g. pi-tasks TaskExecute) naturally
49
+ // forward serializable values, so options.model can be a string like
50
+ // "openai-codex/gpt-5.5". Resolve it to a real Model instance here
51
+ // — same pattern the scheduler path already uses — so the spawned
52
+ // agent's auth lookup doesn't crash with "No API key found for
53
+ // undefined".
54
+ let normalizedOptions = options ?? {};
55
+ if (typeof normalizedOptions.model === "string") {
56
+ const registry = ctx.modelRegistry;
57
+ if (!registry) {
58
+ throw new Error(`Model override "${normalizedOptions.model}" provided but ctx.modelRegistry is unavailable`);
59
+ }
60
+ const resolved = resolveModel(normalizedOptions.model, registry);
61
+ if (typeof resolved === "string") {
62
+ // resolveModel returns a human-readable error string when the
63
+ // input doesn't match any available model. Surface it instead of
64
+ // silently falling back so the caller sees the auth/typo issue.
65
+ throw new Error(resolved);
66
+ }
67
+ normalizedOptions = { ...normalizedOptions, model: resolved };
68
+ }
69
+ return { id: manager.spawn(pi, ctx, type, prompt, normalizedOptions) };
48
70
  });
49
71
  const unsubStop = handleRpc(events, "subagents:rpc:stop", ({ agentId }) => {
50
72
  if (!manager.abort(agentId))
@@ -3,7 +3,7 @@
3
3
  */
4
4
  import { existsSync, readdirSync, readFileSync } from "node:fs";
5
5
  import { basename, join } from "node:path";
6
- import { getAgentDir, parseFrontmatter } from "@mariozechner/pi-coding-agent";
6
+ import { getAgentDir, parseFrontmatter } from "@earendil-works/pi-coding-agent";
7
7
  import { BUILTIN_TOOL_NAMES } from "./agent-types.js";
8
8
  /**
9
9
  * Scan for custom agent .md files from multiple locations.
package/dist/env.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * env.ts — Detect environment info (git, platform) for subagent system prompts.
3
3
  */
4
- import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
4
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
5
5
  import type { EnvInfo } from "./types.js";
6
6
  export declare function detectEnv(pi: ExtensionAPI, cwd: string): Promise<EnvInfo>;
package/dist/index.d.ts CHANGED
@@ -9,5 +9,5 @@
9
9
  * Commands:
10
10
  * /agents — Interactive agent management menu
11
11
  */
12
- import { type ExtensionAPI } from "@mariozechner/pi-coding-agent";
12
+ import { type ExtensionAPI } from "@earendil-works/pi-coding-agent";
13
13
  export default function (pi: ExtensionAPI): void;
package/dist/index.js CHANGED
@@ -11,8 +11,8 @@
11
11
  */
12
12
  import { existsSync, mkdirSync, readFileSync, unlinkSync } from "node:fs";
13
13
  import { join } from "node:path";
14
- import { defineTool, getAgentDir } from "@mariozechner/pi-coding-agent";
15
- import { Text } from "@mariozechner/pi-tui";
14
+ import { defineTool, getAgentDir } from "@earendil-works/pi-coding-agent";
15
+ import { Text } from "@earendil-works/pi-tui";
16
16
  import { Type } from "@sinclair/typebox";
17
17
  import { AgentManager } from "./agent-manager.js";
18
18
  import { getAgentConversation, getDefaultMaxTurns, getGraceTurns, normalizeMaxTurns, setDefaultMaxTurns, setGraceTurns, steerAgent } from "./agent-runner.js";
@@ -4,7 +4,7 @@
4
4
  * Creates a per-agent output file that streams conversation turns as JSONL,
5
5
  * matching Claude Code's task output file format.
6
6
  */
7
- import type { AgentSession } from "@mariozechner/pi-coding-agent";
7
+ import type { AgentSession } from "@earendil-works/pi-coding-agent";
8
8
  /**
9
9
  * Encode a cwd path as a filesystem-safe directory name. Handles:
10
10
  * - POSIX: "/home/user/project" → "home-user-project"
package/dist/prompts.d.ts CHANGED
@@ -19,6 +19,10 @@ export interface PromptExtras {
19
19
  * - "append" mode: env header + parent system prompt + sub-agent context + config.systemPrompt
20
20
  * - "append" with empty systemPrompt: pure parent clone
21
21
  *
22
+ * Both modes prepend an `<active_agent name="${config.name}"/>` tag so downstream
23
+ * extensions (e.g. permission/policy systems) can resolve per-agent policy
24
+ * inside the child session by parsing the system prompt.
25
+ *
22
26
  * @param parentSystemPrompt The parent agent's effective system prompt (for append mode).
23
27
  * @param extras Optional extra sections to inject (memory, preloaded skills).
24
28
  */
package/dist/prompts.js CHANGED
@@ -8,10 +8,15 @@
8
8
  * - "append" mode: env header + parent system prompt + sub-agent context + config.systemPrompt
9
9
  * - "append" with empty systemPrompt: pure parent clone
10
10
  *
11
+ * Both modes prepend an `<active_agent name="${config.name}"/>` tag so downstream
12
+ * extensions (e.g. permission/policy systems) can resolve per-agent policy
13
+ * inside the child session by parsing the system prompt.
14
+ *
11
15
  * @param parentSystemPrompt The parent agent's effective system prompt (for append mode).
12
16
  * @param extras Optional extra sections to inject (memory, preloaded skills).
13
17
  */
14
18
  export function buildAgentPrompt(config, cwd, env, parentSystemPrompt, extras) {
19
+ const activeAgentTag = `<active_agent name="${config.name}"/>\n\n`;
15
20
  const envBlock = `# Environment
16
21
  Working directory: ${cwd}
17
22
  ${env.isGitRepo ? `Git repository: yes\nBranch: ${env.branch}` : "Not a git repository"}
@@ -44,14 +49,14 @@ You are operating as a sub-agent invoked to handle a specific task.
44
49
  const customSection = config.systemPrompt?.trim()
45
50
  ? `\n\n<agent_instructions>\n${config.systemPrompt}\n</agent_instructions>`
46
51
  : "";
47
- return envBlock + "\n\n<inherited_system_prompt>\n" + identity + "\n</inherited_system_prompt>\n\n" + bridge + customSection + extrasSuffix;
52
+ return activeAgentTag + envBlock + "\n\n<inherited_system_prompt>\n" + identity + "\n</inherited_system_prompt>\n\n" + bridge + customSection + extrasSuffix;
48
53
  }
49
54
  // "replace" mode — env header + the config's full system prompt
50
55
  const replaceHeader = `You are a pi coding agent sub-agent.
51
56
  You have been invoked to handle a specific task autonomously.
52
57
 
53
58
  ${envBlock}`;
54
- return replaceHeader + "\n\n" + config.systemPrompt + extrasSuffix;
59
+ return activeAgentTag + replaceHeader + "\n\n" + config.systemPrompt + extrasSuffix;
55
60
  }
56
61
  /** Fallback base prompt when parent system prompt is unavailable in append mode. */
57
62
  const genericBase = `# Role
@@ -17,6 +17,8 @@ export declare class ScheduleStore {
17
17
  private lockPath;
18
18
  private jobs;
19
19
  constructor(filePath: string);
20
+ /** Create the backing directory lazily — only when we're about to persist. */
21
+ private ensureDir;
20
22
  /** Load from disk into the in-memory cache. Silent on parse errors. */
21
23
  private load;
22
24
  /** Atomic write via temp file + rename (POSIX-atomic). */
@@ -64,9 +64,12 @@ export class ScheduleStore {
64
64
  constructor(filePath) {
65
65
  this.filePath = filePath;
66
66
  this.lockPath = filePath + ".lock";
67
- mkdirSync(dirname(filePath), { recursive: true });
68
67
  this.load();
69
68
  }
69
+ /** Create the backing directory lazily — only when we're about to persist. */
70
+ ensureDir() {
71
+ mkdirSync(dirname(this.filePath), { recursive: true });
72
+ }
70
73
  /** Load from disk into the in-memory cache. Silent on parse errors. */
71
74
  load() {
72
75
  if (!existsSync(this.filePath))
@@ -88,6 +91,7 @@ export class ScheduleStore {
88
91
  }
89
92
  /** Acquire lock → reload → mutate → save → release. */
90
93
  withLock(fn) {
94
+ this.ensureDir();
91
95
  acquireLock(this.lockPath);
92
96
  try {
93
97
  this.load();
@@ -120,6 +124,10 @@ export class ScheduleStore {
120
124
  });
121
125
  }
122
126
  update(id, patch) {
127
+ // No-op fast path — an unknown id changes nothing, so don't lock or touch
128
+ // disk (which would otherwise lazily create the backing directory).
129
+ if (!this.jobs.has(id))
130
+ return undefined;
123
131
  return this.withLock(() => {
124
132
  const existing = this.jobs.get(id);
125
133
  if (!existing)
@@ -130,6 +138,9 @@ export class ScheduleStore {
130
138
  });
131
139
  }
132
140
  remove(id) {
141
+ // No-op fast path — see update().
142
+ if (!this.jobs.has(id))
143
+ return false;
133
144
  return this.withLock(() => this.jobs.delete(id));
134
145
  }
135
146
  /** Delete the backing file (used when no jobs remain, optional cleanup). */
@@ -14,7 +14,7 @@
14
14
  * - Result delivery is implicit: spawn → background completion → existing
15
15
  * `subagent-notification` followUp path. No new delivery code.
16
16
  */
17
- import type { ExtensionAPI, ExtensionContext } from "@mariozechner/pi-coding-agent";
17
+ import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
18
18
  import type { AgentManager } from "./agent-manager.js";
19
19
  import type { ScheduleStore } from "./schedule-store.js";
20
20
  import type { IsolationMode, ScheduledSubagent, SubagentType, ThinkingLevel } from "./types.js";
package/dist/settings.js CHANGED
@@ -3,7 +3,7 @@
3
3
  // - Project: <cwd>/.pi/subagents.json — written by /agents → Settings; overrides global on load
4
4
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
5
5
  import { dirname, join } from "node:path";
6
- import { getAgentDir } from "@mariozechner/pi-coding-agent";
6
+ import { getAgentDir } from "@earendil-works/pi-coding-agent";
7
7
  const VALID_JOIN_MODES = new Set(["async", "group", "smart"]);
8
8
  // Sanity ceilings — prevent hand-edited configs from asking for values that
9
9
  // make no operational sense (e.g. 1e6 concurrent subagents). Permissive enough
@@ -20,7 +20,7 @@
20
20
  import { existsSync, readdirSync } from "node:fs";
21
21
  import { homedir } from "node:os";
22
22
  import { join } from "node:path";
23
- import { getAgentDir } from "@mariozechner/pi-coding-agent";
23
+ import { getAgentDir } from "@earendil-works/pi-coding-agent";
24
24
  import { isSymlink, isUnsafeName, safeReadFile } from "./memory.js";
25
25
  export function preloadSkills(skillNames, cwd) {
26
26
  return skillNames.map((name) => ({ name, content: loadSkillContent(name, cwd) }));
package/dist/types.d.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * types.ts — Type definitions for the subagent system.
3
3
  */
4
- import type { ThinkingLevel } from "@mariozechner/pi-agent-core";
5
- import type { AgentSession } from "@mariozechner/pi-coding-agent";
4
+ import type { ThinkingLevel } from "@earendil-works/pi-ai";
5
+ import type { AgentSession } from "@earendil-works/pi-coding-agent";
6
6
  import type { LifetimeUsage } from "./usage.js";
7
7
  export type { ThinkingLevel };
8
8
  /** Agent type: any string name (built-in defaults or user-defined). */
@@ -4,7 +4,7 @@
4
4
  * Displays a tree of agents with animated spinners, live stats, and activity descriptions.
5
5
  * Uses the callback form of setWidget for themed rendering.
6
6
  */
7
- import { truncateToWidth } from "@mariozechner/pi-tui";
7
+ import { truncateToWidth } from "@earendil-works/pi-tui";
8
8
  import { getConfig } from "../agent-types.js";
9
9
  import { getLifetimeTotal, getSessionContextPercent } from "../usage.js";
10
10
  // ---- Constants ----
@@ -4,8 +4,8 @@
4
4
  * Displays a scrollable, live-updating view of an agent's conversation.
5
5
  * Subscribes to session events for real-time streaming updates.
6
6
  */
7
- import type { AgentSession } from "@mariozechner/pi-coding-agent";
8
- import { type Component, type TUI } from "@mariozechner/pi-tui";
7
+ import type { AgentSession } from "@earendil-works/pi-coding-agent";
8
+ import { type Component, type TUI } from "@earendil-works/pi-tui";
9
9
  import type { AgentRecord } from "../types.js";
10
10
  import type { Theme } from "./agent-widget.js";
11
11
  import { type AgentActivity } from "./agent-widget.js";
@@ -4,7 +4,7 @@
4
4
  * Displays a scrollable, live-updating view of an agent's conversation.
5
5
  * Subscribes to session events for real-time streaming updates.
6
6
  */
7
- import { matchesKey, truncateToWidth, visibleWidth, wrapTextWithAnsi } from "@mariozechner/pi-tui";
7
+ import { matchesKey, truncateToWidth, visibleWidth, wrapTextWithAnsi } from "@earendil-works/pi-tui";
8
8
  import { extractText } from "../context.js";
9
9
  import { getLifetimeTotal, getSessionContextPercent } from "../usage.js";
10
10
  import { buildInvocationTags, describeActivity, formatDuration, formatSessionTokens, getDisplayName, getPromptModeLabel } from "./agent-widget.js";
@@ -7,7 +7,7 @@
7
7
  * "I scheduled something dumb, get rid of it"). Add management surfaces here
8
8
  * if real demand emerges.
9
9
  */
10
- import type { ExtensionCommandContext } from "@mariozechner/pi-coding-agent";
10
+ import type { ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
11
11
  import type { SubagentScheduler } from "../schedule.js";
12
12
  /**
13
13
  * List scheduled jobs; selecting one opens a cancel-confirm with details.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tintinweb/pi-subagents",
3
- "version": "0.7.2",
3
+ "version": "0.8.0",
4
4
  "description": "A pi extension extension that brings smart Claude Code-style autonomous sub-agents to pi.",
5
5
  "author": "tintinweb",
6
6
  "license": "MIT",
@@ -21,9 +21,9 @@
21
21
  "autonomous"
22
22
  ],
23
23
  "peerDependencies": {
24
- "@mariozechner/pi-ai": ">=0.70.5",
25
- "@mariozechner/pi-coding-agent": ">=0.70.5",
26
- "@mariozechner/pi-tui": ">=0.70.5"
24
+ "@earendil-works/pi-ai": ">=0.74.0",
25
+ "@earendil-works/pi-coding-agent": ">=0.74.0",
26
+ "@earendil-works/pi-tui": ">=0.74.0"
27
27
  },
28
28
  "dependencies": {
29
29
  "@sinclair/typebox": "^0.34.49",
@@ -7,8 +7,8 @@
7
7
  */
8
8
 
9
9
  import { randomUUID } from "node:crypto";
10
- import type { Model } from "@mariozechner/pi-ai";
11
- import type { AgentSession, ExtensionAPI, ExtensionContext } from "@mariozechner/pi-coding-agent";
10
+ import type { Model } from "@earendil-works/pi-ai";
11
+ import type { AgentSession, ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
12
12
  import { resumeAgent, runAgent, type ToolActivity } from "./agent-runner.js";
13
13
  import type { AgentInvocation, AgentRecord, IsolationMode, SubagentType, ThinkingLevel } from "./types.js";
14
14
  import { addUsage } from "./usage.js";
@@ -183,6 +183,7 @@ export class AgentManager {
183
183
 
184
184
  const promise = runAgent(ctx, type, prompt, {
185
185
  pi,
186
+ agentId: id,
186
187
  model: options.model,
187
188
  maxTurns: options.maxTurns,
188
189
  isolated: options.isolated,
@@ -2,8 +2,8 @@
2
2
  * agent-runner.ts — Core execution engine: creates sessions, runs agents, collects results.
3
3
  */
4
4
 
5
- import type { Model } from "@mariozechner/pi-ai";
6
- import type { ExtensionContext } from "@mariozechner/pi-coding-agent";
5
+ import type { Model } from "@earendil-works/pi-ai";
6
+ import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
7
7
  import {
8
8
  type AgentSession,
9
9
  type AgentSessionEvent,
@@ -13,7 +13,7 @@ import {
13
13
  getAgentDir,
14
14
  SessionManager,
15
15
  SettingsManager,
16
- } from "@mariozechner/pi-coding-agent";
16
+ } from "@earendil-works/pi-coding-agent";
17
17
  import { getAgentConfig, getConfig, getMemoryToolNames, getReadOnlyMemoryToolNames, getToolNamesForType } from "./agent-types.js";
18
18
  import { buildParentContext, extractText } from "./context.js";
19
19
  import { DEFAULT_AGENTS } from "./default-agents.js";
@@ -88,6 +88,8 @@ export interface ToolActivity {
88
88
  export interface RunOptions {
89
89
  /** ExtensionAPI instance — used for pi.exec() instead of execSync. */
90
90
  pi: ExtensionAPI;
91
+ /** Manager-assigned id; suffixes session name to disambiguate parallel spawns (e.g. `Explore#a1b2c3d4`). */
92
+ agentId?: string;
91
93
  model?: Model<any>;
92
94
  maxTurns?: number;
93
95
  signal?: AbortSignal;
@@ -280,6 +282,11 @@ export async function runAgent(
280
282
 
281
283
  const { session } = await createAgentSession(sessionOpts);
282
284
 
285
+ const baseSessionName = agentConfig?.name ?? type;
286
+ session.setSessionName(
287
+ options.agentId ? `${baseSessionName}#${options.agentId.slice(0, 8)}` : baseSessionName,
288
+ );
289
+
283
290
  // Build disallowed tools set from agent config
284
291
  const disallowedSet = agentConfig?.disallowedTools
285
292
  ? new Set(agentConfig.disallowedTools)
package/src/context.ts CHANGED
@@ -2,7 +2,7 @@
2
2
  * context.ts — Extract parent conversation context for subagent inheritance.
3
3
  */
4
4
 
5
- import type { ExtensionContext } from "@mariozechner/pi-coding-agent";
5
+ import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
6
6
 
7
7
  /** Extract text from a message content block array. */
8
8
  export function extractText(content: unknown[]): string {
@@ -9,6 +9,8 @@
9
9
  * error → { success: false, error: string }
10
10
  */
11
11
 
12
+ import { type ModelRegistry, resolveModel } from "./model-resolver.js";
13
+
12
14
  /** Minimal event bus interface needed by the RPC handlers. */
13
15
  export interface EventBus {
14
16
  on(event: string, handler: (data: unknown) => void): () => void;
@@ -81,7 +83,32 @@ export function registerRpcHandlers(deps: RpcDeps): RpcHandle {
81
83
  events, "subagents:rpc:spawn", ({ type, prompt, options }) => {
82
84
  const ctx = getCtx();
83
85
  if (!ctx) throw new Error("No active session");
84
- return { id: manager.spawn(pi, ctx, type, prompt, options ?? {}) };
86
+
87
+ // Cross-extension RPC callers (e.g. pi-tasks TaskExecute) naturally
88
+ // forward serializable values, so options.model can be a string like
89
+ // "openai-codex/gpt-5.5". Resolve it to a real Model instance here
90
+ // — same pattern the scheduler path already uses — so the spawned
91
+ // agent's auth lookup doesn't crash with "No API key found for
92
+ // undefined".
93
+ let normalizedOptions = options ?? {};
94
+ if (typeof normalizedOptions.model === "string") {
95
+ const registry = (ctx as { modelRegistry?: ModelRegistry }).modelRegistry;
96
+ if (!registry) {
97
+ throw new Error(
98
+ `Model override "${normalizedOptions.model}" provided but ctx.modelRegistry is unavailable`,
99
+ );
100
+ }
101
+ const resolved = resolveModel(normalizedOptions.model, registry);
102
+ if (typeof resolved === "string") {
103
+ // resolveModel returns a human-readable error string when the
104
+ // input doesn't match any available model. Surface it instead of
105
+ // silently falling back so the caller sees the auth/typo issue.
106
+ throw new Error(resolved);
107
+ }
108
+ normalizedOptions = { ...normalizedOptions, model: resolved };
109
+ }
110
+
111
+ return { id: manager.spawn(pi, ctx, type, prompt, normalizedOptions) };
85
112
  },
86
113
  );
87
114
 
@@ -4,7 +4,7 @@
4
4
 
5
5
  import { existsSync, readdirSync, readFileSync } from "node:fs";
6
6
  import { basename, join } from "node:path";
7
- import { getAgentDir, parseFrontmatter } from "@mariozechner/pi-coding-agent";
7
+ import { getAgentDir, parseFrontmatter } from "@earendil-works/pi-coding-agent";
8
8
  import { BUILTIN_TOOL_NAMES } from "./agent-types.js";
9
9
  import type { AgentConfig, MemoryScope, ThinkingLevel } from "./types.js";
10
10
 
package/src/env.ts CHANGED
@@ -2,7 +2,7 @@
2
2
  * env.ts — Detect environment info (git, platform) for subagent system prompts.
3
3
  */
4
4
 
5
- import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
5
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
6
6
  import type { EnvInfo } from "./types.js";
7
7
 
8
8
  export async function detectEnv(pi: ExtensionAPI, cwd: string): Promise<EnvInfo> {
package/src/index.ts CHANGED
@@ -12,8 +12,8 @@
12
12
 
13
13
  import { existsSync, mkdirSync, readFileSync, unlinkSync } from "node:fs";
14
14
  import { join } from "node:path";
15
- import { defineTool, type ExtensionAPI, type ExtensionCommandContext, type ExtensionContext, getAgentDir } from "@mariozechner/pi-coding-agent";
16
- import { Text } from "@mariozechner/pi-tui";
15
+ import { defineTool, type ExtensionAPI, type ExtensionCommandContext, type ExtensionContext, getAgentDir } from "@earendil-works/pi-coding-agent";
16
+ import { Text } from "@earendil-works/pi-tui";
17
17
  import { Type } from "@sinclair/typebox";
18
18
  import { AgentManager } from "./agent-manager.js";
19
19
  import { getAgentConversation, getDefaultMaxTurns, getGraceTurns, normalizeMaxTurns, setDefaultMaxTurns, setGraceTurns, steerAgent } from "./agent-runner.js";
@@ -8,7 +8,7 @@
8
8
  import { appendFileSync, chmodSync, mkdirSync, writeFileSync } from "node:fs";
9
9
  import { tmpdir } from "node:os";
10
10
  import { join } from "node:path";
11
- import type { AgentSession, AgentSessionEvent } from "@mariozechner/pi-coding-agent";
11
+ import type { AgentSession, AgentSessionEvent } from "@earendil-works/pi-coding-agent";
12
12
 
13
13
  /**
14
14
  * Encode a cwd path as a filesystem-safe directory name. Handles:
package/src/prompts.ts CHANGED
@@ -19,6 +19,10 @@ export interface PromptExtras {
19
19
  * - "append" mode: env header + parent system prompt + sub-agent context + config.systemPrompt
20
20
  * - "append" with empty systemPrompt: pure parent clone
21
21
  *
22
+ * Both modes prepend an `<active_agent name="${config.name}"/>` tag so downstream
23
+ * extensions (e.g. permission/policy systems) can resolve per-agent policy
24
+ * inside the child session by parsing the system prompt.
25
+ *
22
26
  * @param parentSystemPrompt The parent agent's effective system prompt (for append mode).
23
27
  * @param extras Optional extra sections to inject (memory, preloaded skills).
24
28
  */
@@ -29,6 +33,8 @@ export function buildAgentPrompt(
29
33
  parentSystemPrompt?: string,
30
34
  extras?: PromptExtras,
31
35
  ): string {
36
+ const activeAgentTag = `<active_agent name="${config.name}"/>\n\n`;
37
+
32
38
  const envBlock = `# Environment
33
39
  Working directory: ${cwd}
34
40
  ${env.isGitRepo ? `Git repository: yes\nBranch: ${env.branch}` : "Not a git repository"}
@@ -66,7 +72,7 @@ You are operating as a sub-agent invoked to handle a specific task.
66
72
  ? `\n\n<agent_instructions>\n${config.systemPrompt}\n</agent_instructions>`
67
73
  : "";
68
74
 
69
- return envBlock + "\n\n<inherited_system_prompt>\n" + identity + "\n</inherited_system_prompt>\n\n" + bridge + customSection + extrasSuffix;
75
+ return activeAgentTag + envBlock + "\n\n<inherited_system_prompt>\n" + identity + "\n</inherited_system_prompt>\n\n" + bridge + customSection + extrasSuffix;
70
76
  }
71
77
 
72
78
  // "replace" mode — env header + the config's full system prompt
@@ -75,7 +81,7 @@ You have been invoked to handle a specific task autonomously.
75
81
 
76
82
  ${envBlock}`;
77
83
 
78
- return replaceHeader + "\n\n" + config.systemPrompt + extrasSuffix;
84
+ return activeAgentTag + replaceHeader + "\n\n" + config.systemPrompt + extrasSuffix;
79
85
  }
80
86
 
81
87
  /** Fallback base prompt when parent system prompt is unavailable in append mode. */
@@ -62,10 +62,14 @@ export class ScheduleStore {
62
62
  constructor(filePath: string) {
63
63
  this.filePath = filePath;
64
64
  this.lockPath = filePath + ".lock";
65
- mkdirSync(dirname(filePath), { recursive: true });
66
65
  this.load();
67
66
  }
68
67
 
68
+ /** Create the backing directory lazily — only when we're about to persist. */
69
+ private ensureDir(): void {
70
+ mkdirSync(dirname(this.filePath), { recursive: true });
71
+ }
72
+
69
73
  /** Load from disk into the in-memory cache. Silent on parse errors. */
70
74
  private load(): void {
71
75
  if (!existsSync(this.filePath)) return;
@@ -86,6 +90,7 @@ export class ScheduleStore {
86
90
 
87
91
  /** Acquire lock → reload → mutate → save → release. */
88
92
  private withLock<T>(fn: () => T): T {
93
+ this.ensureDir();
89
94
  acquireLock(this.lockPath);
90
95
  try {
91
96
  this.load();
@@ -121,6 +126,9 @@ export class ScheduleStore {
121
126
  }
122
127
 
123
128
  update(id: string, patch: Partial<ScheduledSubagent>): ScheduledSubagent | undefined {
129
+ // No-op fast path — an unknown id changes nothing, so don't lock or touch
130
+ // disk (which would otherwise lazily create the backing directory).
131
+ if (!this.jobs.has(id)) return undefined;
124
132
  return this.withLock(() => {
125
133
  const existing = this.jobs.get(id);
126
134
  if (!existing) return undefined;
@@ -131,6 +139,8 @@ export class ScheduleStore {
131
139
  }
132
140
 
133
141
  remove(id: string): boolean {
142
+ // No-op fast path — see update().
143
+ if (!this.jobs.has(id)) return false;
134
144
  return this.withLock(() => this.jobs.delete(id));
135
145
  }
136
146
 
package/src/schedule.ts CHANGED
@@ -15,7 +15,7 @@
15
15
  * `subagent-notification` followUp path. No new delivery code.
16
16
  */
17
17
 
18
- import type { ExtensionAPI, ExtensionContext } from "@mariozechner/pi-coding-agent";
18
+ import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
19
19
  import { Cron } from "croner";
20
20
  import { nanoid } from "nanoid";
21
21
  import type { AgentManager } from "./agent-manager.js";
package/src/settings.ts CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
6
6
  import { dirname, join } from "node:path";
7
- import { getAgentDir } from "@mariozechner/pi-coding-agent";
7
+ import { getAgentDir } from "@earendil-works/pi-coding-agent";
8
8
  import type { JoinMode } from "./types.js";
9
9
 
10
10
  export interface SubagentsSettings {
@@ -22,7 +22,7 @@ import type { Dirent } from "node:fs";
22
22
  import { existsSync, readdirSync } from "node:fs";
23
23
  import { homedir } from "node:os";
24
24
  import { join } from "node:path";
25
- import { getAgentDir } from "@mariozechner/pi-coding-agent";
25
+ import { getAgentDir } from "@earendil-works/pi-coding-agent";
26
26
  import { isSymlink, isUnsafeName, safeReadFile } from "./memory.js";
27
27
 
28
28
  export interface PreloadedSkill {
package/src/types.ts CHANGED
@@ -2,8 +2,8 @@
2
2
  * types.ts — Type definitions for the subagent system.
3
3
  */
4
4
 
5
- import type { ThinkingLevel } from "@mariozechner/pi-agent-core";
6
- import type { AgentSession } from "@mariozechner/pi-coding-agent";
5
+ import type { ThinkingLevel } from "@earendil-works/pi-ai";
6
+ import type { AgentSession } from "@earendil-works/pi-coding-agent";
7
7
  import type { LifetimeUsage } from "./usage.js";
8
8
 
9
9
  export type { ThinkingLevel };
@@ -5,7 +5,7 @@
5
5
  * Uses the callback form of setWidget for themed rendering.
6
6
  */
7
7
 
8
- import { truncateToWidth } from "@mariozechner/pi-tui";
8
+ import { truncateToWidth } from "@earendil-works/pi-tui";
9
9
  import type { AgentManager } from "../agent-manager.js";
10
10
  import { getConfig } from "../agent-types.js";
11
11
  import type { AgentInvocation, SubagentType } from "../types.js";
@@ -5,8 +5,8 @@
5
5
  * Subscribes to session events for real-time streaming updates.
6
6
  */
7
7
 
8
- import type { AgentSession } from "@mariozechner/pi-coding-agent";
9
- import { type Component, matchesKey, type TUI, truncateToWidth, visibleWidth, wrapTextWithAnsi } from "@mariozechner/pi-tui";
8
+ import type { AgentSession } from "@earendil-works/pi-coding-agent";
9
+ import { type Component, matchesKey, type TUI, truncateToWidth, visibleWidth, wrapTextWithAnsi } from "@earendil-works/pi-tui";
10
10
  import { extractText } from "../context.js";
11
11
  import type { AgentRecord } from "../types.js";
12
12
  import { getLifetimeTotal, getSessionContextPercent } from "../usage.js";
@@ -8,7 +8,7 @@
8
8
  * if real demand emerges.
9
9
  */
10
10
 
11
- import type { ExtensionCommandContext } from "@mariozechner/pi-coding-agent";
11
+ import type { ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
12
12
  import type { SubagentScheduler } from "../schedule.js";
13
13
  import type { ScheduledSubagent } from "../types.js";
14
14