ai-fob 1.11.2 → 1.11.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/assets/pi/extensions/inter-agent-team/README.md +54 -1
- package/assets/pi/extensions/inter-agent-team/index.ts +5 -2
- package/assets/pi/extensions/inter-agent-team/schema.ts +16 -1
- package/assets/pi/extensions/inter-agent-team/tool.ts +40 -2
- package/assets/pi/extensions/inter-agent-team/types.ts +53 -0
- package/assets/pi/extensions/inter-agent-team/widget.ts +23 -5
- package/assets/pi/extensions/inter-agent-team/workflows/build-feature/manager.ts +370 -0
- package/assets/pi/extensions/inter-agent-team/workflows/build-feature/paths.ts +39 -0
- package/assets/pi/extensions/inter-agent-team/workflows/build-feature/phase-report.ts +77 -0
- package/assets/pi/extensions/inter-agent-team/workflows/build-feature/plan.ts +31 -0
- package/assets/pi/extensions/inter-agent-team/workflows/build-feature/state-store.ts +50 -0
- package/assets/pi/extensions/inter-agent-team/workflows/build-feature/team-launcher.ts +53 -0
- package/assets/pi/prompts/build-feature-interagent.md +92 -331
- package/manifest.json +1 -1
- package/package.json +1 -1
|
@@ -6,7 +6,7 @@ Project-local MVP extension for persistent, PTY-backed teammates.
|
|
|
6
6
|
|
|
7
7
|
- Runtime: Pi by default; experimental terminal-level Claude Code support via `runtime: "claude-code"`
|
|
8
8
|
- Tool: `inter_agent_team`
|
|
9
|
-
- Actions: `create`, `list`, `show`, `send`, `attach`, `close_agent`, `close_team`, `kill_agent`
|
|
9
|
+
- Actions: `create`, `list`, `show`, `send`, `attach`, `close_agent`, `close_team`, `kill_agent`, `start_build_feature`, `status_build_feature`, `resume_build_feature`, `stop_build_feature`
|
|
10
10
|
- Storage: `.pi/inter-agent-team/`
|
|
11
11
|
- Nested team spawning: disabled by policy/environment guard
|
|
12
12
|
|
|
@@ -67,6 +67,59 @@ This launches roughly:
|
|
|
67
67
|
claude --dangerously-skip-permissions "tell me about this repo"
|
|
68
68
|
```
|
|
69
69
|
|
|
70
|
+
## Extension-backed build-feature monitor
|
|
71
|
+
|
|
72
|
+
The extension can run a deterministic multi-phase build-feature monitor that launches Claude Code teammates phase-by-phase and polls filesystem completion reports.
|
|
73
|
+
|
|
74
|
+
Start:
|
|
75
|
+
|
|
76
|
+
```ts
|
|
77
|
+
inter_agent_team({
|
|
78
|
+
action: "start_build_feature",
|
|
79
|
+
plan: "specs/07_instagram-x-creator-feed/07_instagram-x-creator-feed_V1.md",
|
|
80
|
+
startPhase: 2,
|
|
81
|
+
endPhase: 5,
|
|
82
|
+
pollSeconds: 600,
|
|
83
|
+
closeTeams: true
|
|
84
|
+
})
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Status:
|
|
88
|
+
|
|
89
|
+
```ts
|
|
90
|
+
inter_agent_team({ action: "status_build_feature", plan: "specs/.../plan.md" })
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Resume after interruption/restart:
|
|
94
|
+
|
|
95
|
+
```ts
|
|
96
|
+
inter_agent_team({ action: "resume_build_feature", plan: "specs/.../plan.md" })
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Stop monitor:
|
|
100
|
+
|
|
101
|
+
```ts
|
|
102
|
+
inter_agent_team({ action: "stop_build_feature", plan: "specs/.../plan.md", closeTeams: false })
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Workflow state is stored under:
|
|
106
|
+
|
|
107
|
+
```txt
|
|
108
|
+
.pi/inter-agent-team/workflows/build-feature/<feature-slug>.json
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
The monitor uses phase reports as the primary completion contract:
|
|
112
|
+
|
|
113
|
+
```txt
|
|
114
|
+
{SPEC_DIR}/phase<N>_*/phase_completion_report.md
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
and launches each phase with Claude Code:
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
claude --dangerously-skip-permissions "/build-phase-V2 <plan> <phase>"
|
|
121
|
+
```
|
|
122
|
+
|
|
70
123
|
## Attach behavior
|
|
71
124
|
|
|
72
125
|
`attach` is modal and captures keyboard focus. Use it only when the user explicitly wants to take over or interact directly with the teammate terminal.
|
|
@@ -2,12 +2,14 @@ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
|
2
2
|
import { getSessionManager } from "./runtime/session-manager.js";
|
|
3
3
|
import { registerInterAgentTeamTool } from "./tool.js";
|
|
4
4
|
import { registerInterAgentTeamWidget } from "./widget.js";
|
|
5
|
+
import { getBuildFeatureWorkflowManager } from "./workflows/build-feature/manager.js";
|
|
5
6
|
|
|
6
7
|
export default function interAgentTeamExtension(pi: ExtensionAPI): void {
|
|
7
8
|
const manager = getSessionManager();
|
|
9
|
+
const buildFeatureManager = getBuildFeatureWorkflowManager(manager);
|
|
8
10
|
|
|
9
|
-
registerInterAgentTeamTool(pi, manager);
|
|
10
|
-
registerInterAgentTeamWidget(pi, manager);
|
|
11
|
+
registerInterAgentTeamTool(pi, manager, buildFeatureManager);
|
|
12
|
+
registerInterAgentTeamWidget(pi, manager, buildFeatureManager);
|
|
11
13
|
|
|
12
14
|
pi.on("session_start", (_event, ctx) => {
|
|
13
15
|
if (process.env.PI_INTER_AGENT_TEAM_CAN_SPAWN === "false") {
|
|
@@ -16,6 +18,7 @@ export default function interAgentTeamExtension(pi: ExtensionAPI): void {
|
|
|
16
18
|
});
|
|
17
19
|
|
|
18
20
|
pi.on("session_shutdown", async () => {
|
|
21
|
+
buildFeatureManager.stopAllTimers();
|
|
19
22
|
await manager.closeAll({ force: true, reason: "pi_session_shutdown" });
|
|
20
23
|
});
|
|
21
24
|
}
|
|
@@ -11,6 +11,10 @@ export const InterAgentTeamParams = Type.Object({
|
|
|
11
11
|
"close_agent",
|
|
12
12
|
"close_team",
|
|
13
13
|
"kill_agent",
|
|
14
|
+
"start_build_feature",
|
|
15
|
+
"status_build_feature",
|
|
16
|
+
"resume_build_feature",
|
|
17
|
+
"stop_build_feature",
|
|
14
18
|
] as const),
|
|
15
19
|
|
|
16
20
|
team: Type.Optional(Type.String({ description: "Team id or name" })),
|
|
@@ -20,6 +24,13 @@ export const InterAgentTeamParams = Type.Object({
|
|
|
20
24
|
message: Type.Optional(Type.String({ description: "Message to send to a teammate" })),
|
|
21
25
|
task: Type.Optional(Type.String({ description: "Initial team task" })),
|
|
22
26
|
|
|
27
|
+
plan: Type.Optional(Type.String({ description: "High-level build plan path for build-feature workflows" })),
|
|
28
|
+
workflow: Type.Optional(Type.String({ description: "Build-feature workflow id, feature slug, or plan path" })),
|
|
29
|
+
startPhase: Type.Optional(Type.Number({ description: "First phase to run" })),
|
|
30
|
+
endPhase: Type.Optional(Type.Number({ description: "Last phase to run" })),
|
|
31
|
+
pollSeconds: Type.Optional(Type.Number({ description: "Polling interval in seconds for build-feature workflows" })),
|
|
32
|
+
closeTeams: Type.Optional(Type.Boolean({ description: "Whether completed phase teams should be closed automatically" })),
|
|
33
|
+
|
|
23
34
|
agents: Type.Optional(
|
|
24
35
|
Type.Array(
|
|
25
36
|
Type.Object({
|
|
@@ -43,4 +54,8 @@ export type InterAgentTeamAction =
|
|
|
43
54
|
| "attach"
|
|
44
55
|
| "close_agent"
|
|
45
56
|
| "close_team"
|
|
46
|
-
| "kill_agent"
|
|
57
|
+
| "kill_agent"
|
|
58
|
+
| "start_build_feature"
|
|
59
|
+
| "status_build_feature"
|
|
60
|
+
| "resume_build_feature"
|
|
61
|
+
| "stop_build_feature";
|
|
@@ -6,6 +6,8 @@ import type { TeamAgentRecord, TeamRecord, ToolDetails } from "./types.js";
|
|
|
6
6
|
import { getRuntimeAdapter } from "./runtime/runtime-adapter.js";
|
|
7
7
|
import type { InterAgentSessionManager } from "./runtime/session-manager.js";
|
|
8
8
|
import { TeammateOverlay } from "./ui/teammate-overlay.js";
|
|
9
|
+
import type { BuildFeatureWorkflowManager } from "./workflows/build-feature/manager.js";
|
|
10
|
+
import { formatBuildFeatureWorkflow } from "./workflows/build-feature/manager.js";
|
|
9
11
|
|
|
10
12
|
function textResult(text: string, details: ToolDetails): AgentToolResult<ToolDetails> {
|
|
11
13
|
return { content: [{ type: "text", text }], details };
|
|
@@ -47,7 +49,7 @@ function syncTeamStatuses(team: TeamRecord, manager: InterAgentSessionManager):
|
|
|
47
49
|
return team;
|
|
48
50
|
}
|
|
49
51
|
|
|
50
|
-
export function registerInterAgentTeamTool(pi: ExtensionAPI, manager: InterAgentSessionManager): void {
|
|
52
|
+
export function registerInterAgentTeamTool(pi: ExtensionAPI, manager: InterAgentSessionManager, buildFeatureManager?: BuildFeatureWorkflowManager): void {
|
|
51
53
|
pi.registerTool({
|
|
52
54
|
name: "inter_agent_team",
|
|
53
55
|
label: "Inter-Agent Team",
|
|
@@ -60,13 +62,49 @@ export function registerInterAgentTeamTool(pi: ExtensionAPI, manager: InterAgent
|
|
|
60
62
|
"Do not use inter_agent_team attach automatically after create, list, show, or send.",
|
|
61
63
|
"Do not use inter_agent_team for simple one-shot delegation; use subagent for disposable tasks instead.",
|
|
62
64
|
"Do not create nested inter-agent teams from inside a teammate session.",
|
|
65
|
+
"Use start_build_feature/status_build_feature/resume_build_feature/stop_build_feature for extension-backed multi-phase build orchestration instead of manual long-running polling.",
|
|
63
66
|
],
|
|
64
67
|
parameters: InterAgentTeamParams,
|
|
65
68
|
|
|
66
69
|
async execute(_toolCallId, params: any, _signal, _onUpdate, ctx: any) {
|
|
67
70
|
try {
|
|
68
71
|
switch (params.action) {
|
|
69
|
-
case "
|
|
72
|
+
case "start_build_feature": {
|
|
73
|
+
if (!buildFeatureManager) throw new Error("Build-feature workflow manager is not available.");
|
|
74
|
+
const plan = requireString(params.plan, "plan");
|
|
75
|
+
const workflow = await buildFeatureManager.start({
|
|
76
|
+
cwd: ctx.cwd ?? process.cwd(),
|
|
77
|
+
plan,
|
|
78
|
+
startPhase: typeof params.startPhase === "number" ? params.startPhase : undefined,
|
|
79
|
+
endPhase: typeof params.endPhase === "number" ? params.endPhase : undefined,
|
|
80
|
+
pollSeconds: typeof params.pollSeconds === "number" ? params.pollSeconds : undefined,
|
|
81
|
+
closeTeams: typeof params.closeTeams === "boolean" ? params.closeTeams : undefined,
|
|
82
|
+
});
|
|
83
|
+
return textResult(`Started build-feature workflow.\n\n${formatBuildFeatureWorkflow(workflow)}`, { ok: true, workflow });
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
case "status_build_feature": {
|
|
87
|
+
if (!buildFeatureManager) throw new Error("Build-feature workflow manager is not available.");
|
|
88
|
+
const result = await buildFeatureManager.status({ cwd: ctx.cwd ?? process.cwd(), workflow: params.workflow, plan: params.plan });
|
|
89
|
+
if (result.workflow) return textResult(formatBuildFeatureWorkflow(result.workflow), { ok: true, workflow: result.workflow });
|
|
90
|
+
const workflows = result.workflows ?? [];
|
|
91
|
+
const text = workflows.length ? workflows.map(formatBuildFeatureWorkflow).join("\n\n") : "No build-feature workflows found.";
|
|
92
|
+
return textResult(text, { ok: true, workflows });
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
case "resume_build_feature": {
|
|
96
|
+
if (!buildFeatureManager) throw new Error("Build-feature workflow manager is not available.");
|
|
97
|
+
const workflow = await buildFeatureManager.resume({ cwd: ctx.cwd ?? process.cwd(), workflow: params.workflow, plan: params.plan });
|
|
98
|
+
return textResult(`Resumed build-feature workflow.\n\n${formatBuildFeatureWorkflow(workflow)}`, { ok: true, workflow });
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
case "stop_build_feature": {
|
|
102
|
+
if (!buildFeatureManager) throw new Error("Build-feature workflow manager is not available.");
|
|
103
|
+
const workflow = await buildFeatureManager.stop({ cwd: ctx.cwd ?? process.cwd(), workflow: params.workflow, plan: params.plan, closeTeams: params.closeTeams === true });
|
|
104
|
+
return textResult(`Stopped build-feature workflow.\n\n${formatBuildFeatureWorkflow(workflow)}`, { ok: true, workflow });
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
case "create": {
|
|
70
108
|
if (process.env.PI_INTER_AGENT_TEAM_CAN_SPAWN === "false") {
|
|
71
109
|
throw new Error("Nested inter-agent-team creation is disabled for teammate sessions.");
|
|
72
110
|
}
|
|
@@ -57,11 +57,64 @@ export interface CreateTeamInput {
|
|
|
57
57
|
cwd?: string;
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
+
export type BuildFeatureWorkflowStatus = "running" | "completed" | "blocked" | "failed" | "stopped" | "interrupted";
|
|
61
|
+
|
|
62
|
+
export type BuildFeaturePhaseStatus = "pending" | "running" | "completed" | "blocked" | "failed" | "interrupted" | "skipped";
|
|
63
|
+
|
|
64
|
+
export interface BuildFeaturePhaseRecord {
|
|
65
|
+
phase: number;
|
|
66
|
+
status: BuildFeaturePhaseStatus;
|
|
67
|
+
teamId?: string;
|
|
68
|
+
teamName?: string;
|
|
69
|
+
agentId?: string;
|
|
70
|
+
agentName?: string;
|
|
71
|
+
launchPrompt?: string;
|
|
72
|
+
startedAt?: string;
|
|
73
|
+
completedAt?: string;
|
|
74
|
+
lastPollAt?: string;
|
|
75
|
+
completionReport?: string;
|
|
76
|
+
result?: "complete" | "blocked" | "failed" | "unknown";
|
|
77
|
+
validationResult?: "pass" | "pass-with-followups" | "blocked" | "fail" | "unknown" | string;
|
|
78
|
+
stateHashBefore?: string;
|
|
79
|
+
stateHashAfter?: string;
|
|
80
|
+
note?: string;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export interface BuildFeatureWorkflowRecord {
|
|
84
|
+
workflow: "build-feature-interagent";
|
|
85
|
+
id: string;
|
|
86
|
+
status: BuildFeatureWorkflowStatus;
|
|
87
|
+
planPath: string;
|
|
88
|
+
planPathAbs: string;
|
|
89
|
+
projectRoot: string;
|
|
90
|
+
specDir: string;
|
|
91
|
+
featureSlug: string;
|
|
92
|
+
taskName: string;
|
|
93
|
+
teamName: string;
|
|
94
|
+
createdAt: string;
|
|
95
|
+
updatedAt: string;
|
|
96
|
+
startedAt?: string;
|
|
97
|
+
completedAt?: string;
|
|
98
|
+
stoppedAt?: string;
|
|
99
|
+
startPhase: number;
|
|
100
|
+
endPhase: number;
|
|
101
|
+
totalPhases: number;
|
|
102
|
+
currentPhase: number;
|
|
103
|
+
pollSeconds: number;
|
|
104
|
+
closeTeams: boolean;
|
|
105
|
+
lastPollAt?: string;
|
|
106
|
+
nextPollAt?: string;
|
|
107
|
+
lastMessage?: string;
|
|
108
|
+
phases: BuildFeaturePhaseRecord[];
|
|
109
|
+
}
|
|
110
|
+
|
|
60
111
|
export interface ToolDetails {
|
|
61
112
|
ok: boolean;
|
|
62
113
|
team?: TeamRecord;
|
|
63
114
|
teams?: TeamRecord[];
|
|
64
115
|
agent?: TeamAgentRecord;
|
|
116
|
+
workflow?: BuildFeatureWorkflowRecord;
|
|
117
|
+
workflows?: BuildFeatureWorkflowRecord[];
|
|
65
118
|
output?: string;
|
|
66
119
|
error?: string;
|
|
67
120
|
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import { getProjectRegistry } from "./registry.js";
|
|
3
3
|
import type { InterAgentSessionManager, ManagedTeammateSession } from "./runtime/session-manager.js";
|
|
4
|
-
import type { TeamAgentRecord, TeamRecord } from "./types.js";
|
|
4
|
+
import type { TeamAgentRecord, TeamRecord, BuildFeatureWorkflowRecord } from "./types.js";
|
|
5
|
+
import type { BuildFeatureWorkflowManager } from "./workflows/build-feature/manager.js";
|
|
5
6
|
|
|
6
7
|
const WIDGET_KEY = "inter-agent-team";
|
|
7
8
|
|
|
@@ -28,8 +29,19 @@ function renderAgentLine(team: TeamRecord, agent: TeamAgentRecord, live: Managed
|
|
|
28
29
|
return `${theme.fg(role(status), icon(status))} ${theme.fg("text", agent.name)} ${theme.fg("muted", `[${agent.runtime}]`)} ${theme.fg(role(status), `— ${suffix}`)}`;
|
|
29
30
|
}
|
|
30
31
|
|
|
31
|
-
function
|
|
32
|
+
function renderWorkflowLine(workflow: BuildFeatureWorkflowRecord, theme: any): string {
|
|
33
|
+
const phase = workflow.phases.find((item) => item.phase === workflow.currentPhase);
|
|
34
|
+
const next = workflow.nextPollAt ? ` next poll ${workflow.nextPollAt}` : "";
|
|
35
|
+
return `${theme.fg(role(workflow.status), icon(workflow.status))} ${theme.fg("text", workflow.featureSlug)} ${theme.fg("muted", `— ${workflow.status}; phase ${workflow.currentPhase}/${workflow.endPhase}${phase ? ` ${phase.status}` : ""}${next}`)}`;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function renderLines(activeTeams: TeamRecord[], liveSessions: ManagedTeammateSession[], workflows: BuildFeatureWorkflowRecord[], theme: any): string[] {
|
|
32
39
|
const lines: string[] = [theme.fg("accent", "Inter-Agent Teams")];
|
|
40
|
+
const activeWorkflows = workflows.filter((workflow) => workflow.status === "running" || workflow.status === "blocked" || workflow.status === "failed" || workflow.status === "interrupted");
|
|
41
|
+
if (activeWorkflows.length > 0) {
|
|
42
|
+
lines.push(theme.fg("accent", "Build Feature Workflows"));
|
|
43
|
+
for (const workflow of activeWorkflows) lines.push(renderWorkflowLine(workflow, theme));
|
|
44
|
+
}
|
|
33
45
|
for (const team of activeTeams) {
|
|
34
46
|
lines.push(theme.fg("muted", `Team: ${team.name} (${team.id}) — ${team.status}`));
|
|
35
47
|
if (team.agents.length === 0) {
|
|
@@ -44,7 +56,7 @@ function renderLines(activeTeams: TeamRecord[], liveSessions: ManagedTeammateSes
|
|
|
44
56
|
return lines;
|
|
45
57
|
}
|
|
46
58
|
|
|
47
|
-
export function registerInterAgentTeamWidget(pi: ExtensionAPI, manager: InterAgentSessionManager): void {
|
|
59
|
+
export function registerInterAgentTeamWidget(pi: ExtensionAPI, manager: InterAgentSessionManager, buildFeatureManager?: BuildFeatureWorkflowManager): void {
|
|
48
60
|
let lastCtx: any | null = null;
|
|
49
61
|
let repainting = false;
|
|
50
62
|
let repaintQueued = false;
|
|
@@ -59,11 +71,13 @@ export function registerInterAgentTeamWidget(pi: ExtensionAPI, manager: InterAge
|
|
|
59
71
|
try {
|
|
60
72
|
const registry = getProjectRegistry(ctx.cwd ?? process.cwd());
|
|
61
73
|
const activeTeams = (await registry.listTeams()).filter((team) => team.status === "active");
|
|
62
|
-
|
|
74
|
+
const workflows = buildFeatureManager ? await buildFeatureManager.list(ctx.cwd ?? process.cwd()) : [];
|
|
75
|
+
const visibleWorkflows = workflows.filter((workflow) => workflow.status === "running" || workflow.status === "blocked" || workflow.status === "failed" || workflow.status === "interrupted");
|
|
76
|
+
if (activeTeams.length === 0 && visibleWorkflows.length === 0) {
|
|
63
77
|
ctx.ui.setWidget(WIDGET_KEY, undefined);
|
|
64
78
|
return;
|
|
65
79
|
}
|
|
66
|
-
ctx.ui.setWidget(WIDGET_KEY, renderLines(activeTeams, manager.list(), ctx.ui.theme), { placement: "belowEditor" });
|
|
80
|
+
ctx.ui.setWidget(WIDGET_KEY, renderLines(activeTeams, manager.list(), workflows, ctx.ui.theme), { placement: "belowEditor" });
|
|
67
81
|
} finally {
|
|
68
82
|
repainting = false;
|
|
69
83
|
if (repaintQueued) {
|
|
@@ -77,6 +91,10 @@ export function registerInterAgentTeamWidget(pi: ExtensionAPI, manager: InterAge
|
|
|
77
91
|
if (lastCtx) void repaint(lastCtx);
|
|
78
92
|
});
|
|
79
93
|
|
|
94
|
+
buildFeatureManager?.onChange(() => {
|
|
95
|
+
if (lastCtx) void repaint(lastCtx);
|
|
96
|
+
});
|
|
97
|
+
|
|
80
98
|
pi.on("session_start", (_event: any, ctx: any) => {
|
|
81
99
|
lastCtx = ctx;
|
|
82
100
|
void repaint(ctx);
|