pi-open-agents 0.1.24 → 0.1.25

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-open-agents",
3
- "version": "0.1.24",
3
+ "version": "0.1.25",
4
4
  "description": "Unified agent and subagent management for pi coding agent, with OpenCode-compatible agent definitions.",
5
5
  "keywords": [
6
6
  "pi-package",
@@ -92,6 +92,9 @@ export async function loadAgents(options: LoadAgentsOptions = {}): Promise<Agent
92
92
  const projectAgents = new Map<string, AgentDefinition>();
93
93
  const warnings: AgentLoadResult["warnings"] = [];
94
94
 
95
+ const GLOBAL_FAMILY_RANK: Record<string, number> = { pi: 2, opencode: 1, shared: 1 };
96
+ const globalRanks = new Map<string, number>();
97
+
95
98
  for (const discPath of paths) {
96
99
  // For each subdir pattern, scan the directory
97
100
  for (const subdir of discPath.subdirs) {
@@ -113,8 +116,16 @@ export async function loadAgents(options: LoadAgentsOptions = {}): Promise<Agent
113
116
  if (agent.disable) continue;
114
117
 
115
118
  // Add to the appropriate map
116
- const target = discPath.source === "project" ? projectAgents : globalAgents;
117
- target.set(agent.name, agent);
119
+ if (discPath.source === "project") {
120
+ projectAgents.set(agent.name, agent);
121
+ } else {
122
+ const rank = GLOBAL_FAMILY_RANK[discPath.family] ?? 0;
123
+ const prevRank = globalRanks.get(agent.name);
124
+ if (prevRank === undefined || rank >= prevRank) {
125
+ globalAgents.set(agent.name, agent);
126
+ globalRanks.set(agent.name, rank);
127
+ }
128
+ }
118
129
  } catch (error) {
119
130
  warnings.push({
120
131
  filePath,
package/src/index.ts CHANGED
@@ -22,7 +22,7 @@ import type { AgentDefinition, ThinkingLevel } from "./types.ts";
22
22
  import { loadAgents, selectableAgents, spawnableAgents } from "./discovery/loader.ts";
23
23
  import { AgentManager } from "./primary/manager.ts";
24
24
  import { refreshAgentTools, buildSystemPrompt, buildSubagentGuidance } from "./primary/executor.ts";
25
- import { updateBanner } from "./tui/banner.ts";
25
+ import { setShowAgentDescription, updateBanner } from "./tui/banner.ts";
26
26
  import { registerTuiControls } from "./tui/selector.ts";
27
27
  import { registerAgentTools } from "./tui/tools.ts";
28
28
  import { registerSubagentTool } from "./subagent/tool.ts";
@@ -34,6 +34,7 @@ import { registerPermissionGate } from "./permission/gate.ts";
34
34
  interface Settings {
35
35
  defaultAgent?: string;
36
36
  defaultThinkingLevel?: ThinkingLevel;
37
+ showAgentDescription?: boolean;
37
38
  }
38
39
 
39
40
  function loadSettings(cwd: string): Settings {
@@ -75,6 +76,7 @@ export default function piOpenAgents(pi: ExtensionAPI): void {
75
76
  pi.on("session_start", async (event, ctx) => {
76
77
  // Load settings for thinking fallback
77
78
  const settings = loadSettings(ctx.cwd);
79
+ setShowAgentDescription(settings.showAgentDescription);
78
80
 
79
81
  // Load all agent definitions from discovery paths
80
82
  const result = await loadAgents({
@@ -115,6 +117,16 @@ export default function piOpenAgents(pi: ExtensionAPI): void {
115
117
  // (--model, --system-prompt, --tools). Do NOT apply defaultAgent — it would
116
118
  // override the executor's model/tools/prompt settings.
117
119
  if (isSubagentProcess(process.env)) {
120
+ // A forked child inherits the parent's session entries (pi core copies
121
+ // them into the branched session) and never re-announces (this early
122
+ // return). Without this, the permission system would resolve the
123
+ // parent's active_agent entry and attribute the parent's policy to the
124
+ // child. Append an explicit terminator so the child falls back to the
125
+ // global policy — the pre-patch baseline. Falls back to the global policy
126
+ // because nothing has resolved an agent name yet in this process — the
127
+ // permission system caches the last name it resolved (knownAgentName) and
128
+ // does not clear it between sessions.
129
+ pi.appendEntry("active_agent", { name: null });
118
130
  updateBanner(ctx.ui, undefined, 0);
119
131
  return;
120
132
  }
@@ -140,10 +152,26 @@ export default function piOpenAgents(pi: ExtensionAPI): void {
140
152
 
141
153
  if (agentEntry?.data?.name) {
142
154
  manager.restoreFromSession(agentEntry.data.name);
143
- // Re-apply tools on resume (model preserved from session)
155
+ // Only announce the agent when the restore actually produced an active
156
+ // agent. restoreFromSession() silently no-ops on an unknown name
157
+ // (deleted, renamed, or disable:true — the loader skips those), and
158
+ // announcing one would make the permission system apply a disabled
159
+ // agent's policy to this session.
144
160
  const active = manager.getActive();
145
161
  if (active) {
162
+ pi.appendEntry("active_agent", { name: active.name });
163
+ // Re-apply tools on resume (model preserved from session)
146
164
  refreshAgentTools(active, pi);
165
+ } else {
166
+ // The restore no-op'd (see above), so no new entry was written. An
167
+ // older active_agent entry written by a previous apply() is still the
168
+ // newest one, and the consumer's backward scan would resolve it —
169
+ // silently applying that (now disabled/unknown) agent's policy.
170
+ // Append an explicit terminator so it stops here and falls back to
171
+ // the global policy — nothing has resolved an agent name yet in this
172
+ // process, since the consumer caches the last name it resolved
173
+ // (knownAgentName) and does not clear it between sessions.
174
+ pi.appendEntry("active_agent", { name: null });
147
175
  }
148
176
  updateBanner(ctx.ui, manager.getActive(), selectableAgents(manager.getAgents()).length);
149
177
  return;
@@ -69,6 +69,7 @@ export class AgentManager {
69
69
  this.active = agent;
70
70
 
71
71
  await applyAgentConfig(agent, pi, ctx);
72
+ pi.appendEntry("active_agent", { name });
72
73
 
73
74
  return true;
74
75
  }
@@ -93,6 +94,10 @@ export class AgentManager {
93
94
 
94
95
  this.activeName = undefined;
95
96
  this.active = undefined;
97
+ // The consumer treats a null-name entry as "stop scanning" and then falls
98
+ // back to its last known name, so clearing does not restore the global
99
+ // policy for a session that already resolved a non-null agent.
100
+ pi.appendEntry("active_agent", { name: null });
96
101
  }
97
102
 
98
103
  /**
package/src/tui/banner.ts CHANGED
@@ -1,12 +1,22 @@
1
1
  /**
2
2
  * Status banner widget for the TUI.
3
3
  *
4
- * Shows the active agent name, model, and description above the editor.
4
+ * Shows the active agent name, description (when enabled), and model above the editor.
5
5
  */
6
6
 
7
7
  import type { ExtensionUIContext } from "@earendil-works/pi-coding-agent";
8
8
  import type { AgentDefinition } from "../types.ts";
9
9
 
10
+ let showDescription = true;
11
+
12
+ /**
13
+ * Configure whether the active agent's description is shown in the status banner.
14
+ * Absent/undefined falls back to true, preserving the default behavior.
15
+ */
16
+ export function setShowAgentDescription(value: boolean | undefined): void {
17
+ showDescription = value ?? true;
18
+ }
19
+
10
20
  /**
11
21
  * Update the status widget to reflect the active agent.
12
22
  * Pass undefined to show "no agent selected" hint.
@@ -20,7 +30,7 @@ export function updateBanner(
20
30
  const parts: string[] = [];
21
31
  parts.push(ui.theme.bold(ui.theme.fg("accent", activeAgent.name)));
22
32
 
23
- if (activeAgent.description) {
33
+ if (showDescription && activeAgent.description) {
24
34
  parts.push(ui.theme.fg("muted", `— ${activeAgent.description}`));
25
35
  }
26
36