@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
|
-
|
|
184
|
-
|
|
185
|
-
|
|
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.
|
|
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.
|
|
43
|
-
"@mariozechner/pi-ai": "^0.
|
|
44
|
-
"@mariozechner/pi-coding-agent": "^0.
|
|
45
|
-
"@mariozechner/pi-tui": "^0.
|
|
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",
|
package/scripts/smoke-test.mts
CHANGED
|
@@ -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
|
|
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(
|
|
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
|
{
|