@henryqw/pi-subagent 27.4.1 → 27.4.3

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/README.md CHANGED
@@ -178,7 +178,7 @@ Role Markdown files in `~/.pi/agent/config/pi-subagent/` require frontmatter and
178
178
  | `tools` | Required base or internal tool-name array; `[]` selects none. Name `codemode` or `git_read` to activate it. |
179
179
  | `extensions` | Required array of trusted absolute paths, supported package sources, or Pi built-ins such as `builtin:codemode` |
180
180
  | `skills` | Required array of effective Pi Skill names |
181
- | `mcps` | Optional exact global server names from `~/.pi/agent/mcp.json`, using native `mcpServers` format. Omitted or `[]` denies MCP access. |
181
+ | `mcps` | Optional exact global server names from `~/.pi/agent/mcp.json`, using native `mcpServers` format. Omitted or `[]` denies access to `mcp.json` servers. Servers that Role extensions register always connect. |
182
182
  | body | Required system instructions |
183
183
 
184
184
  The package ships `implementer`, `reviewer`, and `scout`; same-name user Roles override built-ins. Roles define capabilities, not access or isolation. Unknown frontmatter keys invalidate that file and report its path and key. Other valid Roles load; duplicate names become unavailable. Delegation to an invalid file's claimed Role fails with that file's error. Inspect `/subagent roles`.
package/dist/index.js CHANGED
@@ -471,7 +471,8 @@ function prepareRoleLaunchFromSkills(ctx, input, skills) {
471
471
  const extensions = [
472
472
  ...selectedExtensions,
473
473
  ...(CODEX_ALIAS.test(input.route.model.provider) ? [MULTI_CODEX_EXTENSION] : []),
474
- ...(mcps.length ? [ROLE_MCP_EXTENSION] : []),
474
+ // Always loaded: it connects the servers that Role extensions register; `mcps` gates only mcp.json.
475
+ ROLE_MCP_EXTENSION,
475
476
  ...(role.tools.includes("git_read") ? [GIT_READ_EXTENSION] : []),
476
477
  ROLE_TOOLS_EXTENSION,
477
478
  ];
@@ -483,8 +484,7 @@ function prepareRoleLaunchFromSkills(ctx, input, skills) {
483
484
  return [key, value];
484
485
  }));
485
486
  const args = ["--no-session", "--no-extensions", "--no-skills", "--exclude-tools", CHILD_EXCLUDED_TOOL_NAMES.join(",")];
486
- if (mcps.length)
487
- args.push(`--${ROLE_MCP_POLICY_FLAG}`, JSON.stringify(mcps));
487
+ args.push(`--${ROLE_MCP_POLICY_FLAG}`, JSON.stringify(mcps));
488
488
  for (const extension of new Set(extensions))
489
489
  args.push("--extension", extension);
490
490
  for (const skill of skills.paths)
@@ -469,6 +469,9 @@ export class IntegrationGit {
469
469
  return { outcome: "blocked", failure: "Worktree registration/path/branch do not match approved tip." };
470
470
  if (!sameIdentity(await current(root, info, signal), expected))
471
471
  return { outcome: "blocked", failure: "Worktree differs from approved clean candidate." };
472
+ const dirty = await inspectWorktreeDirty(info.path, (args, cwd) => git(args, cwd, signal));
473
+ if (dirty.failure || dirty.dirty)
474
+ return { outcome: "blocked", failure: "Owned checkout has ignored, untracked or tracked changes; cleanup preserves them." };
472
475
  const args = ["worktree", "remove", info.path];
473
476
  const removed = await git(args, root, signal);
474
477
  if (removed.code !== 0)
package/dist/mcp-role.js CHANGED
@@ -120,6 +120,9 @@ function validateRoleMcpServer(path, name, value, codemode) {
120
120
  * never activated by MCP configuration: the Role tool policy owns the active set.
121
121
  */
122
122
  export function loadRoleMcpConfig(agentDir, allowlist, options = { codemode: false }) {
123
+ // A Role without mcps must not depend on mcp.json being readable.
124
+ if (!allowlist.length)
125
+ return { servers: [], autoEnableCodemode: false, errors: [] };
123
126
  const path = join(agentDir, "mcp.json");
124
127
  let parsed = {};
125
128
  try {
@@ -410,6 +410,8 @@ To let a Role run `codemode` scripts, list `builtin:codemode` in `extensions`. A
410
410
 
411
411
  Pi's `codemode.mode` and `codemode.inlineBudget` settings apply in the child without change. So the Role never forces codemode-only operation. MCP configuration never activates `codemode` in a child. Scripts can call only the active tools of the Role, plus `codemode` and `deferred` extension and MCP tools. Inactive base tools such as `bash` stay unreachable.
412
412
 
413
+ Every child loads the Role MCP adapter. It also connects the servers that Role extensions register with `pi.registerMcpServer()`, for example the CodeGraph watcher of `pi-codegraph`. These servers keep the exposure that the extension sets, and `mcps` does not gate them, because the Role already trusts the extension.
414
+
413
415
  The servers named in `mcps` come only from the global `~/.pi/agent/mcp.json`. In a Role that activates `codemode`, each selected server keeps its configured `exposure` and `toolExposure`. Pi's default `codemode` exposure makes tools callable from scripts without declarations. `direct` declares them. `deferred` tools stay reachable from scripts, and `hidden` tools stay unreachable. An invalid exposure value fails the launch.
414
416
 
415
417
  In every other Role, the extension forces selected servers to `direct` exposure. It ignores `toolExposure`, because that child cannot reach undeclared tools. Declared tools show to the model as `mcp__<server>__<tool>` after the server connects. Pi replaces `-` in server and tool names with `_`. Pi appends a hash suffix when two tool names collide or a name is longer than 64 characters.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@henryqw/pi-subagent",
3
- "version": "27.4.1",
3
+ "version": "27.4.3",
4
4
  "description": "Delegate bounded direct work or durable checked isolated task graphs to Pi roles.",
5
5
  "keywords": [
6
6
  "pi-package",