pi-extended-teams 2.1.16

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.
Files changed (130) hide show
  1. package/README.md +105 -0
  2. package/assets/pi-extended-teams-agent-navigation.png +0 -0
  3. package/assets/pi-extended-teams-in-action.png +0 -0
  4. package/extensions/agents/read-agent-report.ts +181 -0
  5. package/extensions/agents/read-agent-session-lifecycle.test.ts +605 -0
  6. package/extensions/agents/read-agent-session-lifecycle.ts +676 -0
  7. package/extensions/agents/read-agent.test.ts +3077 -0
  8. package/extensions/agents/read-agent.ts +1119 -0
  9. package/extensions/agents/write-agent.test.ts +513 -0
  10. package/extensions/agents/write-agent.ts +392 -0
  11. package/extensions/events/register-events.test.ts +465 -0
  12. package/extensions/events/register-events.ts +409 -0
  13. package/extensions/index.test.ts +1659 -0
  14. package/extensions/index.ts +1228 -0
  15. package/extensions/internal/agent-session-files.test.ts +164 -0
  16. package/extensions/internal/agent-session-files.ts +320 -0
  17. package/extensions/internal/debug.ts +44 -0
  18. package/extensions/internal/model-selection.ts +82 -0
  19. package/extensions/internal/pi-command.test.ts +191 -0
  20. package/extensions/internal/pi-command.ts +224 -0
  21. package/extensions/internal/pi-runtime-api.test.ts +43 -0
  22. package/extensions/internal/pi-runtime-api.ts +78 -0
  23. package/extensions/internal/schema.ts +19 -0
  24. package/extensions/internal/session-context-reference.test.ts +290 -0
  25. package/extensions/internal/session-context-reference.ts +430 -0
  26. package/extensions/internal/session-files.test.ts +225 -0
  27. package/extensions/internal/session-files.ts +273 -0
  28. package/extensions/internal/session-usage.ts +59 -0
  29. package/extensions/resources/spawn-resource-plan.test.ts +233 -0
  30. package/extensions/resources/spawn-resource-plan.ts +247 -0
  31. package/extensions/runtime/active-agent-sleep.test.ts +157 -0
  32. package/extensions/runtime/active-agent-sleep.ts +117 -0
  33. package/extensions/runtime/nested-read-agents.ts +22 -0
  34. package/extensions/runtime/pending-child-controller.test.ts +169 -0
  35. package/extensions/runtime/pending-child-controller.ts +280 -0
  36. package/extensions/runtime/types.ts +68 -0
  37. package/extensions/team/contracts.test.ts +111 -0
  38. package/extensions/team/lifecycle.test.ts +1095 -0
  39. package/extensions/team/lifecycle.ts +502 -0
  40. package/extensions/team/recipient-closure.test.ts +97 -0
  41. package/extensions/team/recipient-closure.ts +120 -0
  42. package/extensions/team/roster.test.ts +129 -0
  43. package/extensions/team/roster.ts +165 -0
  44. package/extensions/team/team-contracts.json +43 -0
  45. package/extensions/team/writer-screens.test.ts +50 -0
  46. package/extensions/team/writer-screens.ts +156 -0
  47. package/extensions/tools/agent-communication-tools.test.ts +306 -0
  48. package/extensions/tools/agent-communication-tools.ts +183 -0
  49. package/extensions/tools/coordination-tools.test.ts +670 -0
  50. package/extensions/tools/coordination-tools.ts +304 -0
  51. package/extensions/tools/delegation-guard.test.ts +95 -0
  52. package/extensions/tools/delegation-guard.ts +65 -0
  53. package/extensions/tools/file-claim-tools.test.ts +104 -0
  54. package/extensions/tools/file-claim-tools.ts +68 -0
  55. package/extensions/tools/model-tools.ts +53 -0
  56. package/extensions/tools/predefined-tools.test.ts +647 -0
  57. package/extensions/tools/predefined-tools.ts +331 -0
  58. package/extensions/tools/read-helper.test.ts +35 -0
  59. package/extensions/tools/task-runtime-tools.test.ts +334 -0
  60. package/extensions/tools/task-runtime-tools.ts +227 -0
  61. package/extensions/tools/team-tools.read-agent.test.ts +1924 -0
  62. package/extensions/tools/team-tools.ts +1270 -0
  63. package/extensions/ui/agent-follow-view.test.ts +520 -0
  64. package/extensions/ui/agent-follow-view.ts +779 -0
  65. package/extensions/ui/agent-navigation.test.ts +52 -0
  66. package/extensions/ui/agent-navigation.ts +55 -0
  67. package/extensions/ui/ansi.ts +16 -0
  68. package/extensions/ui/extensions-command.test.ts +336 -0
  69. package/extensions/ui/extensions-command.ts +282 -0
  70. package/extensions/ui/favorite-models-command.test.ts +280 -0
  71. package/extensions/ui/favorite-models-command.ts +423 -0
  72. package/extensions/ui/frame.ts +77 -0
  73. package/extensions/ui/input.ts +17 -0
  74. package/extensions/ui/read-agent-status.test.ts +117 -0
  75. package/extensions/ui/read-agent-status.ts +125 -0
  76. package/extensions/ui/renderers.ts +243 -0
  77. package/extensions/ui/status-widget.test.ts +143 -0
  78. package/extensions/ui/status-widget.ts +440 -0
  79. package/extensions/ui-frame.test.ts +69 -0
  80. package/package.json +75 -0
  81. package/skills/teams.md +256 -0
  82. package/src/adapters/terminal-registry.ts +78 -0
  83. package/src/adapters/tmux-adapter.test.ts +276 -0
  84. package/src/adapters/tmux-adapter.ts +200 -0
  85. package/src/orchestration/index.ts +437 -0
  86. package/src/orchestration/orchestrator.test.ts +396 -0
  87. package/src/orchestration/types.ts +125 -0
  88. package/src/utils/atomic-json.ts +21 -0
  89. package/src/utils/claims.test.ts +137 -0
  90. package/src/utils/claims.ts +169 -0
  91. package/src/utils/hooks.test.ts +75 -0
  92. package/src/utils/hooks.ts +35 -0
  93. package/src/utils/lifecycle-tombstone.test.ts +105 -0
  94. package/src/utils/lifecycle-tombstone.ts +268 -0
  95. package/src/utils/lock.race.child.ts +44 -0
  96. package/src/utils/lock.race.test.ts +198 -0
  97. package/src/utils/lock.test.ts +90 -0
  98. package/src/utils/lock.ts +186 -0
  99. package/src/utils/messaging.test.ts +337 -0
  100. package/src/utils/messaging.ts +441 -0
  101. package/src/utils/model-resolution.test.ts +231 -0
  102. package/src/utils/model-resolution.ts +322 -0
  103. package/src/utils/models.test.ts +8 -0
  104. package/src/utils/models.ts +115 -0
  105. package/src/utils/paths.ts +81 -0
  106. package/src/utils/predefined-teams/types.ts +48 -0
  107. package/src/utils/predefined-teams.save-template.test.ts +74 -0
  108. package/src/utils/predefined-teams.test.ts +441 -0
  109. package/src/utils/predefined-teams.ts +471 -0
  110. package/src/utils/read-helper-queue.ts +99 -0
  111. package/src/utils/report-events.test.ts +154 -0
  112. package/src/utils/report-events.ts +222 -0
  113. package/src/utils/runtime.test.ts +314 -0
  114. package/src/utils/runtime.ts +261 -0
  115. package/src/utils/security.test.ts +43 -0
  116. package/src/utils/settings.test.ts +480 -0
  117. package/src/utils/settings.ts +645 -0
  118. package/src/utils/shared-memory.test.ts +80 -0
  119. package/src/utils/shared-memory.ts +81 -0
  120. package/src/utils/tasks.race.test.ts +44 -0
  121. package/src/utils/tasks.test.ts +229 -0
  122. package/src/utils/tasks.ts +396 -0
  123. package/src/utils/teams.ts +231 -0
  124. package/src/utils/terminal-adapter.ts +103 -0
  125. package/src/utils/thinking-levels.test.ts +56 -0
  126. package/src/utils/thinking-levels.ts +47 -0
  127. package/src/utils/workflow-metadata.test.ts +11 -0
  128. package/src/utils/workflow-metadata.ts +88 -0
  129. package/src/utils/write-queue.test.ts +251 -0
  130. package/src/utils/write-queue.ts +202 -0
package/README.md ADDED
@@ -0,0 +1,105 @@
1
+ # pi-extended-teams
2
+
3
+ **A control-first subagent system for Pi.**
4
+
5
+ pi-extended-teams lets the lead split work into bounded lanes, watch agents live, steer or stop them, and receive their reports. The lead still makes the final call.
6
+
7
+ Read agents are the default. Edit agents are opt-in and should own isolated files.
8
+
9
+ [![Three agents working in parallel inside Pi](assets/pi-extended-teams-in-action.png)](assets/pi-extended-teams-in-action.png)
10
+
11
+ ## Install and run
12
+
13
+ Install from GitHub:
14
+
15
+ ```bash
16
+ pi install git:github.com/dantetekanem/pi-extended-teams
17
+ ```
18
+
19
+ Run `/agents-favorite-models` once in Pi and choose a model and thinking level for each intent tier.
20
+
21
+ Then ask for help naturally:
22
+
23
+ ```text
24
+ Review the current changes with separate agents for correctness, test gaps, and security. Give me the evidence so I can make the final call.
25
+ ```
26
+
27
+ The current Pi session becomes the agent group automatically. There is no separate setup step.
28
+
29
+ ## How it works
30
+
31
+ - Read and edit agents run in-process and stay connected to the current Pi session.
32
+ - The activity card shows progress, intent tier, elapsed time, tokens, and tool activity.
33
+ - You can open an agent's transcript, send it a message, or stop it.
34
+ - Completed reports return to the lead automatically and remain recoverable when needed.
35
+ - Every spawn uses a configured intent tier instead of ad hoc model settings.
36
+ - Edit agents can claim isolated files. Claims coordinate cooperative agents; they are not access control.
37
+ - Lazy session context and nested read helpers are available when a bounded task needs them.
38
+
39
+ This works well for multi-angle code review, root-cause investigation, parallel verification, repository mapping, and one narrow edit that can stay separate from the lead's work.
40
+
41
+ ## Live control
42
+
43
+ With the editor empty, press Down to open agent navigation. Use Down/Up to move, `l` to expand large tool logs, `m` to message an agent, `x` to stop it, and Escape to return.
44
+
45
+ [![Inspecting and messaging a running agent](assets/pi-extended-teams-agent-navigation.png)](assets/pi-extended-teams-agent-navigation.png)
46
+
47
+ Completed reports wake the lead automatically. Do not poll with sleeps, loops, or repeated status checks. Use `check_teammate` only when one agent appears unhealthy or a persisted report needs recovery.
48
+
49
+ The lead owns decomposition, integration, and acceptance. Pi packages and spawned agents run with your system permissions, so review project-local instructions and configuration through Pi's normal trust flow.
50
+
51
+ ## Intent tiers
52
+
53
+ Every spawn uses a configured `model_slot`:
54
+
55
+ | Tier | Use it for |
56
+ | --- | --- |
57
+ | `read-collect` | Bounded facts, logs, inventories, or test output. |
58
+ | `read-review` | Normal review, verification, and test-gap work. |
59
+ | `read-analyze` | Root-cause analysis across connected evidence. |
60
+ | `read-critical` | Rare high-stakes security, architecture, concurrency, migration, or data reasoning. |
61
+ | `write-patch` | A narrow documentation, config, fixture, or bug fix. |
62
+ | `write-feature` | A bounded feature with a known design. |
63
+ | `write-system` | A cross-cutting integration or refactor inside claimed files. |
64
+ | `write-critical` | Rare high-risk security, concurrency, recovery, migration, or data-integrity work. |
65
+
66
+ `read-review` is the normal read default. Legacy tier names remain accepted for this minor release, but new prompts should use the canonical names above.
67
+
68
+ ## Explicit spawning
69
+
70
+ Pi can choose when delegation helps, or you can call the tools directly:
71
+
72
+ ```text
73
+ spawn_swarm_agents({
74
+ defaults: { model_slot: "read-review" },
75
+ agents: [
76
+ { name: "correctness", prompt: "Review the diff for concrete correctness risks. Do not edit." },
77
+ { name: "tests", prompt: "Find missing regression coverage with file and line evidence. Do not edit." }
78
+ ]
79
+ })
80
+ ```
81
+
82
+ For an edit, choose a write tier and name the files it may claim. Never run overlapping writers against the same paths.
83
+
84
+ ## Configuration
85
+
86
+ Global settings live at `~/.pi/agent/pi-extended-teams/settings.json`. Project overrides live at `.pi/pi-extended-teams.json`. Favorite intent tiers are global so `/agents-favorite-models` and spawning use the same choices.
87
+
88
+ Spawned sessions are private by default under `~/.pi/teams/<team>/agent-sessions/` and stay out of Pi's normal `/resume` picker.
89
+
90
+ ## Development
91
+
92
+ ```bash
93
+ pnpm typecheck
94
+ pnpm test:focused
95
+ ```
96
+
97
+ ## Credits
98
+
99
+ pi-extended-teams is based on [pi-teams](https://github.com/burggraf/pi-teams). This fork focuses on session-connected agents, live control, and a smaller public tool surface.
100
+
101
+ The broader coordination lineage includes [claude-code-teams-mcp](https://github.com/cs50victor/claude-code-teams-mcp).
102
+
103
+ ## License
104
+
105
+ MIT
@@ -0,0 +1,181 @@
1
+ import fs from "node:fs";
2
+ import type { AgentReportSource } from "../runtime/types";
3
+ import { getLastAssistantText } from "../ui/renderers";
4
+ import type { SubmittedAgentReport } from "../tools/agent-communication-tools";
5
+
6
+ export const EMPTY_REPORT_RECOVERY_PROMPT = [
7
+ "Your previous turn ended without a usable final report.",
8
+ "Do not perform more investigation or edits.",
9
+ "Using only the evidence already gathered in this session, call report_and_exit now with the complete non-empty deliverable requested by the original assignment.",
10
+ "If the assignment could not be completed, call report_and_exit with a non-empty blocker or failure report explaining why.",
11
+ ].join(" ");
12
+
13
+ export interface ResolvedReadAgentReport {
14
+ report?: string;
15
+ summary?: string;
16
+ source?: AgentReportSource;
17
+ terminalFailure?: string;
18
+ recoveryReason?: string;
19
+ }
20
+
21
+ export interface PersistedReadAgentMessages {
22
+ messages: any[];
23
+ error?: string;
24
+ }
25
+
26
+ export class ReadAgentReportUnavailableError extends Error {
27
+ readonly reportSource = "irrecoverable-empty" as const;
28
+
29
+ constructor(
30
+ message: string,
31
+ readonly recoveryAttempted: boolean,
32
+ readonly recoverySessionId?: string,
33
+ readonly recoverySessionFile?: string,
34
+ ) {
35
+ super(message);
36
+ this.name = "ReadAgentReportUnavailableError";
37
+ }
38
+ }
39
+
40
+ export function nonEmptyReportText(value: unknown): string | undefined {
41
+ if (typeof value !== "string") return undefined;
42
+ const text = value.trim();
43
+ return text || undefined;
44
+ }
45
+
46
+ function getAcceptedReportToolSubmission(messages: any[]): SubmittedAgentReport | undefined {
47
+ const acceptedToolCallIds = new Set<string>();
48
+ for (const message of messages || []) {
49
+ if (
50
+ message?.role === "toolResult"
51
+ && message.toolName === "report_and_exit"
52
+ && message.details?.accepted === true
53
+ && typeof message.toolCallId === "string"
54
+ ) {
55
+ acceptedToolCallIds.add(message.toolCallId);
56
+ }
57
+ }
58
+ if (acceptedToolCallIds.size === 0) return undefined;
59
+
60
+ for (let messageIndex = messages.length - 1; messageIndex >= 0; messageIndex--) {
61
+ const message = messages[messageIndex];
62
+ if (message?.role !== "assistant" || !Array.isArray(message.content)) continue;
63
+ for (let partIndex = message.content.length - 1; partIndex >= 0; partIndex--) {
64
+ const part = message.content[partIndex];
65
+ if (
66
+ part?.type !== "toolCall"
67
+ || part.name !== "report_and_exit"
68
+ || !acceptedToolCallIds.has(part.id)
69
+ ) continue;
70
+ const content = nonEmptyReportText(part.arguments?.content);
71
+ if (!content) continue;
72
+ return {
73
+ content,
74
+ summary: nonEmptyReportText(part.arguments?.summary),
75
+ };
76
+ }
77
+ }
78
+ return undefined;
79
+ }
80
+
81
+ function getLastAssistantMessage(messages: any[]): any | undefined {
82
+ for (let index = messages.length - 1; index >= 0; index--) {
83
+ if (messages[index]?.role === "assistant") return messages[index];
84
+ }
85
+ return undefined;
86
+ }
87
+
88
+ function terminalAssistantState(messages: any[]): { failure?: string; recoveryReason?: string } {
89
+ const message = getLastAssistantMessage(messages);
90
+ if (!message) return {};
91
+ const stopReason = typeof message.stopReason === "string" ? message.stopReason : undefined;
92
+ const errorMessage = nonEmptyReportText(message.errorMessage);
93
+ if (stopReason === "error" || stopReason === "aborted") {
94
+ return {
95
+ failure: `The final assistant response ended with stopReason=${stopReason}${errorMessage ? `: ${errorMessage}` : "."}`,
96
+ };
97
+ }
98
+ if (stopReason === "length") {
99
+ return { recoveryReason: "The final assistant response reached its output limit before producing a usable report." };
100
+ }
101
+ return {};
102
+ }
103
+
104
+ export function persistedSessionMessages(
105
+ SessionManager: any,
106
+ sessionFile: string | undefined,
107
+ ): PersistedReadAgentMessages {
108
+ if (!sessionFile || !fs.existsSync(sessionFile)) return { messages: [] };
109
+ try {
110
+ const entries = SessionManager.open(sessionFile).getEntries();
111
+ return {
112
+ messages: entries
113
+ .filter((entry: any) => entry?.type === "message" && entry.message)
114
+ .map((entry: any) => entry.message),
115
+ };
116
+ } catch (error) {
117
+ return { messages: [], error: error instanceof Error ? error.message : String(error) };
118
+ }
119
+ }
120
+
121
+ export function resolveReadAgentReport(
122
+ submittedFinalReport: SubmittedAgentReport | undefined,
123
+ immediateMessages: any[],
124
+ persistedMessages: any[],
125
+ ): ResolvedReadAgentReport {
126
+ const submittedContent = nonEmptyReportText(submittedFinalReport?.content);
127
+ if (submittedContent) {
128
+ return {
129
+ report: submittedContent,
130
+ summary: nonEmptyReportText(submittedFinalReport?.summary),
131
+ source: "report_and_exit",
132
+ };
133
+ }
134
+
135
+ const immediateToolReport = getAcceptedReportToolSubmission(immediateMessages);
136
+ if (immediateToolReport) {
137
+ return { report: immediateToolReport.content, summary: immediateToolReport.summary, source: "report_and_exit" };
138
+ }
139
+ const persistedToolReport = getAcceptedReportToolSubmission(persistedMessages);
140
+ if (persistedToolReport) {
141
+ return {
142
+ report: persistedToolReport.content,
143
+ summary: persistedToolReport.summary,
144
+ source: "persisted-report_and_exit",
145
+ };
146
+ }
147
+
148
+ const terminal = terminalAssistantState(immediateMessages.length > 0 ? immediateMessages : persistedMessages);
149
+ if (terminal.failure) return { terminalFailure: terminal.failure };
150
+ if (terminal.recoveryReason) return { recoveryReason: terminal.recoveryReason };
151
+
152
+ const immediateText = getLastAssistantText(immediateMessages);
153
+ if (immediateText) return { report: immediateText, source: "assistant-text" };
154
+ const persistedText = getLastAssistantText(persistedMessages);
155
+ if (persistedText) return { report: persistedText, source: "persisted-assistant-text" };
156
+ return {};
157
+ }
158
+
159
+ export function readAgentRecoveryReference(
160
+ teamName: string,
161
+ agentName: string,
162
+ runId: string,
163
+ sessionManager: any,
164
+ ): { sessionId?: string; sessionFile?: string; durableFile: boolean; pointer: string; guidance: string } {
165
+ const sessionId = nonEmptyReportText(sessionManager?.getSessionId?.());
166
+ const sessionFile = nonEmptyReportText(sessionManager?.getSessionFile?.());
167
+ const durableFile = !!sessionFile && fs.existsSync(sessionFile);
168
+ const pointer = [
169
+ "pi-child-session/v1",
170
+ `team=${JSON.stringify(teamName)}`,
171
+ `agent=${JSON.stringify(agentName)}`,
172
+ `lifecycleRunId=${JSON.stringify(runId)}`,
173
+ `sessionId=${JSON.stringify(sessionId ?? "unavailable")}`,
174
+ `sessionFile=${JSON.stringify(sessionFile ?? "unavailable")}`,
175
+ `durableFile=${durableFile}`,
176
+ ].join(" ");
177
+ const guidance = durableFile
178
+ ? `Retrieve the child transcript with the read tool at ${JSON.stringify(sessionFile)} or open it with SessionManager.open(${JSON.stringify(sessionFile)}). Inspect newest message entries for an accepted report_and_exit call and assistant text.`
179
+ : "No child JSONL file was materialized; use the team, agent, lifecycle run, and session identifiers above to correlate runtime and report-event diagnostics.";
180
+ return { sessionId, sessionFile, durableFile, pointer, guidance };
181
+ }