@tmustier/pi-agent-teams 0.5.1 → 0.5.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/CHANGELOG.md CHANGED
@@ -1,5 +1,18 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.5.3
4
+
5
+ ### Fixes
6
+
7
+ - **Inherited parent-team attach warnings** — forked/branched child sessions now silently detach back to their own session team when they briefly inherit a parent team id without any attach claim. This removes the noisy warning seen in subagent-style flows while preserving normal `not_owner` warnings for real claim conflicts.
8
+
9
+ ## 0.5.2
10
+
11
+ ### Fixes
12
+
13
+ - **Pi 0.62 metadata compatibility** — updated tool metadata wiring for recent Pi releases so teams tools continue to render the right prompt snippets/guidelines and stay compatible with current core APIs.
14
+ - **Non-interactive exit hang** — leader polling timers now call `unref()` so print/json child sessions can exit cleanly instead of hanging after the agent finishes. This fixes subagent and other nested Pi flows that load the teams extension in the background.
15
+
3
16
  ## 0.5.1
4
17
 
5
18
  ### Features
@@ -191,6 +191,11 @@ export function registerTeamsTool(opts: {
191
191
  "Optional overrides: model='<provider>/<modelId>' and thinking (off|minimal|low|medium|high|xhigh).",
192
192
  "For governance, the user can run /team delegate on (leader restricted to coordination) or /team spawn <name> plan (worker needs plan approval).",
193
193
  ].join(" "),
194
+ promptSnippet: "Delegate work across teammates, inspect member status, message workers, and manage team lifecycle/tasks.",
195
+ promptGuidelines: [
196
+ "Use this tool when the user wants parallel agent work, worker coordination, or team lifecycle/task management.",
197
+ "Prefer member_status before interrupting or reassigning active teammates when the current state is unclear.",
198
+ ],
194
199
  parameters: TeamsToolParamsSchema,
195
200
 
196
201
  async execute(_toolCallId, params: TeamsToolParamsType, signal, _onUpdate, ctx): Promise<AgentToolResult<unknown>> {
@@ -33,6 +33,7 @@ import {
33
33
  } from "./hooks.js";
34
34
  import { handleTeamCommand } from "./leader-team-command.js";
35
35
  import { registerTeamsTool } from "./leader-teams-tool.js";
36
+ import { getParentSessionId, shouldSilenceInheritedParentAttachClaimWarning } from "./session-parent.js";
36
37
  import type { ContextMode, SpawnTeammateFn, SpawnTeammateResult, WorkspaceMode } from "./spawn-types.js";
37
38
 
38
39
  function getTeamsExtensionEntryPath(): string | null {
@@ -155,6 +156,7 @@ export function runLeader(pi: ExtensionAPI): void {
155
156
  let delegateMode = process.env.PI_TEAMS_DELEGATE_MODE === "1";
156
157
  let style: TeamsStyle = getTeamsStyleFromEnv();
157
158
  let lastAttachClaimHeartbeatMs = 0;
159
+ let inheritedParentTeamId: string | null = null;
158
160
 
159
161
  const stopLoops = () => {
160
162
  if (refreshTimer) clearInterval(refreshTimer);
@@ -180,13 +182,22 @@ export function runLeader(pi: ExtensionAPI): void {
180
182
  const result = await heartbeatTeamAttachClaim(getTeamDir(currentTeamId), sessionTeamId);
181
183
  if (result === "updated") return;
182
184
 
183
- ctx.ui.notify(
184
- `Attach claim for team ${currentTeamId} is no longer owned by this session; detaching to session team.`,
185
- "warning",
186
- );
185
+ const lostTeamId = currentTeamId;
186
+ const shouldSilenceWarning = shouldSilenceInheritedParentAttachClaimWarning({
187
+ currentTeamId: lostTeamId,
188
+ parentSessionId: inheritedParentTeamId,
189
+ result,
190
+ });
191
+ inheritedParentTeamId = null;
187
192
  currentTeamId = sessionTeamId;
188
193
  taskListId = sessionTeamId;
189
194
  delegationTracker.clear();
195
+ if (!shouldSilenceWarning) {
196
+ ctx.ui.notify(
197
+ `Attach claim for team ${lostTeamId} is no longer owned by this session; detaching to session team.`,
198
+ "warning",
199
+ );
200
+ }
190
201
  await refreshTasks();
191
202
  renderWidget();
192
203
  };
@@ -709,6 +720,7 @@ export function runLeader(pi: ExtensionAPI): void {
709
720
  pi.on("session_start", async (_event, ctx) => {
710
721
  currentCtx = ctx;
711
722
  currentTeamId = currentCtx.sessionManager.getSessionId();
723
+ inheritedParentTeamId = getParentSessionId(currentCtx.sessionManager);
712
724
  // Keep the task list aligned with the active session. If you want a shared namespace,
713
725
  // use `/team task use <taskListId>` after switching.
714
726
  taskListId = currentTeamId;
@@ -750,6 +762,8 @@ export function runLeader(pi: ExtensionAPI): void {
750
762
  refreshInFlight = false;
751
763
  }
752
764
  }, 1000);
765
+ // Don't keep non-interactive/child pi processes alive just because leader polling exists.
766
+ refreshTimer.unref?.();
753
767
 
754
768
  inboxTimer = setInterval(async () => {
755
769
  if (isStopping) return;
@@ -761,6 +775,7 @@ export function runLeader(pi: ExtensionAPI): void {
761
775
  inboxInFlight = false;
762
776
  }
763
777
  }, 700);
778
+ inboxTimer.unref?.();
764
779
  });
765
780
 
766
781
  pi.on("session_switch", async (_event, ctx) => {
@@ -789,6 +804,7 @@ export function runLeader(pi: ExtensionAPI): void {
789
804
 
790
805
  currentCtx = ctx;
791
806
  currentTeamId = currentCtx.sessionManager.getSessionId();
807
+ inheritedParentTeamId = getParentSessionId(currentCtx.sessionManager);
792
808
  // Keep the task list aligned with the active session. If you want a shared namespace,
793
809
  // use `/team task use <taskListId>` after switching.
794
810
  taskListId = currentTeamId;
@@ -820,6 +836,7 @@ export function runLeader(pi: ExtensionAPI): void {
820
836
  refreshInFlight = false;
821
837
  }
822
838
  }, 1000);
839
+ refreshTimer.unref?.();
823
840
 
824
841
  inboxTimer = setInterval(async () => {
825
842
  if (isStopping) return;
@@ -831,6 +848,7 @@ export function runLeader(pi: ExtensionAPI): void {
831
848
  inboxInFlight = false;
832
849
  }
833
850
  }, 700);
851
+ inboxTimer.unref?.();
834
852
  });
835
853
 
836
854
  pi.on("session_shutdown", async () => {
@@ -1067,6 +1085,7 @@ export function runLeader(pi: ExtensionAPI): void {
1067
1085
  getActiveTeamId: () => currentTeamId ?? ctx.sessionManager.getSessionId(),
1068
1086
  setActiveTeamId: (teamId) => {
1069
1087
  currentTeamId = teamId;
1088
+ inheritedParentTeamId = null;
1070
1089
  delegationTracker.clear();
1071
1090
  },
1072
1091
  pendingPlanApprovals,
@@ -0,0 +1,24 @@
1
+ import { SessionManager } from "@mariozechner/pi-coding-agent";
2
+ import type { TeamAttachClaimHeartbeatResult } from "./team-attach-claim.js";
3
+
4
+ interface SessionManagerWithHeader {
5
+ getHeader(): { parentSession?: string } | null;
6
+ }
7
+
8
+ export function getParentSessionId(sessionManager: SessionManagerWithHeader): string | null {
9
+ const parentSessionPath = sessionManager.getHeader()?.parentSession;
10
+ if (!parentSessionPath) return null;
11
+ try {
12
+ return SessionManager.open(parentSessionPath).getSessionId();
13
+ } catch {
14
+ return null;
15
+ }
16
+ }
17
+
18
+ export function shouldSilenceInheritedParentAttachClaimWarning(opts: {
19
+ currentTeamId: string;
20
+ parentSessionId: string | null;
21
+ result: TeamAttachClaimHeartbeatResult;
22
+ }): boolean {
23
+ return opts.result === "missing" && opts.parentSessionId !== null && opts.currentTeamId === opts.parentSessionId;
24
+ }
@@ -143,6 +143,11 @@ export function runWorker(pi: ExtensionAPI): void {
143
143
  name: "team_message",
144
144
  label: "Team Message",
145
145
  description: "Send a message to a comrade. Use this to coordinate with peers on related tasks. Set urgent=true to interrupt their active turn (use sparingly — only for time-sensitive coordination).",
146
+ promptSnippet: "Send a coordination message to another teammate, optionally as an urgent interruption.",
147
+ promptGuidelines: [
148
+ "Use this tool for teammate-to-teammate coordination instead of overloading task status fields with freeform messages.",
149
+ "Set urgent=true only when the recipient must be interrupted before finishing their current turn.",
150
+ ],
146
151
  parameters: TeamMessageToolParamsSchema,
147
152
  async execute(
148
153
  _toolCallId,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tmustier/pi-agent-teams",
3
- "version": "0.5.1",
3
+ "version": "0.5.3",
4
4
  "description": "Claude Code agent teams style workflow for Pi.",
5
5
  "license": "MIT",
6
6
  "author": "Thomas Mustier",
@@ -39,10 +39,10 @@
39
39
  },
40
40
  "devDependencies": {
41
41
  "@eslint/js": "^9.39.2",
42
- "@mariozechner/pi-agent-core": "^0.52.8",
43
- "@mariozechner/pi-ai": "^0.52.8",
44
- "@mariozechner/pi-coding-agent": "^0.52.8",
45
- "@mariozechner/pi-tui": "^0.52.8",
42
+ "@mariozechner/pi-agent-core": "^0.62.0",
43
+ "@mariozechner/pi-ai": "^0.62.0",
44
+ "@mariozechner/pi-coding-agent": "^0.62.0",
45
+ "@mariozechner/pi-tui": "^0.62.0",
46
46
  "@sinclair/typebox": "^0.34.48",
47
47
  "eslint": "^9.39.2",
48
48
  "tsx": "^4.20.5",
@@ -70,7 +70,9 @@ import {
70
70
  isPlanRejectedMessage,
71
71
  } from "../extensions/teams/protocol.js";
72
72
  import { pollLeaderInbox } from "../extensions/teams/leader-inbox.js";
73
- import type { ExtensionContext } from "@mariozechner/pi-coding-agent";
73
+ import { getParentSessionId, shouldSilenceInheritedParentAttachClaimWarning } from "../extensions/teams/session-parent.js";
74
+ import { SessionManager, type ExtensionContext } from "@mariozechner/pi-coding-agent";
75
+ import type { AssistantMessage } from "@mariozechner/pi-ai";
74
76
 
75
77
  // ── helpers ──────────────────────────────────────────────────────────
76
78
  let passed = 0;
@@ -554,14 +556,18 @@ console.log("\n7. Pi extension loading");
554
556
  return typeof c === "string" ? c : undefined;
555
557
  })();
556
558
 
559
+ const versionOutput = `${res.stdout ?? ""}${res.stderr ?? ""}`.trim();
560
+
557
561
  if (errCode === "ENOENT") {
558
562
  console.log(" (skipped) pi CLI not found on PATH");
559
563
  } else if (errCode === "ETIMEDOUT") {
560
564
  console.log(" (skipped) pi --version timed out");
561
565
  } else if (res.status !== 0) {
562
566
  console.log(" (skipped) pi --version returned non-zero exit code");
567
+ } else if (versionOutput.length === 0) {
568
+ console.log(" (skipped) pi --version produced no output");
563
569
  } else {
564
- assert((res.stdout ?? "").trim().length > 0, "pi --version works");
570
+ assert(versionOutput.length > 0, "pi --version works");
565
571
  }
566
572
  }
567
573
 
@@ -841,6 +847,67 @@ console.log("\n10. team discovery + attach claims");
841
847
  }
842
848
  }
843
849
 
850
+ // ── 10b. branched sessions + inherited attach claims ────────────────
851
+ console.log("\n10b. branched sessions + inherited attach claims");
852
+ {
853
+ const sessionsDir = path.join(tmpRoot, "branch-session-test");
854
+ const parent = SessionManager.create(tmpRoot, sessionsDir);
855
+ const assistantMessage: AssistantMessage = {
856
+ role: "assistant",
857
+ content: [{ type: "text", text: "ok" }],
858
+ api: "test",
859
+ provider: "test",
860
+ model: "test",
861
+ usage: {
862
+ input: 0,
863
+ output: 0,
864
+ cacheRead: 0,
865
+ cacheWrite: 0,
866
+ totalTokens: 0,
867
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
868
+ },
869
+ stopReason: "stop",
870
+ timestamp: Date.now(),
871
+ };
872
+ parent.appendMessage(assistantMessage);
873
+ const parentLeafId = parent.getLeafId();
874
+ const originalParentSessionId = parent.getSessionId();
875
+ assert(parentLeafId !== null, "branch test parent has a leaf entry");
876
+ if (parentLeafId) {
877
+ const branchedPath = parent.createBranchedSession(parentLeafId);
878
+ assert(branchedPath !== null, "createBranchedSession returns child session path");
879
+ if (branchedPath) {
880
+ const child = SessionManager.open(branchedPath, sessionsDir);
881
+ const parentSessionId = getParentSessionId(child);
882
+ assertEq(parentSessionId, originalParentSessionId, "getParentSessionId resolves branch parent session id");
883
+ assert(
884
+ shouldSilenceInheritedParentAttachClaimWarning({
885
+ currentTeamId: originalParentSessionId,
886
+ parentSessionId,
887
+ result: "missing",
888
+ }),
889
+ "silences missing inherited parent-team claim warnings",
890
+ );
891
+ assert(
892
+ !shouldSilenceInheritedParentAttachClaimWarning({
893
+ currentTeamId: originalParentSessionId,
894
+ parentSessionId,
895
+ result: "not_owner",
896
+ }),
897
+ "keeps non-owner inherited parent-team warnings visible",
898
+ );
899
+ assert(
900
+ !shouldSilenceInheritedParentAttachClaimWarning({
901
+ currentTeamId: child.getSessionId(),
902
+ parentSessionId,
903
+ result: "missing",
904
+ }),
905
+ "does not silence unrelated missing-claim warnings",
906
+ );
907
+ }
908
+ }
909
+ }
910
+
844
911
  // ── 11. /team done (end-of-run cleanup) ──────────────────────────────
845
912
  console.log("\n11. /team done (end-of-run)");
846
913
  {