fix: enhance error messages and documentation for team provisioning

- Updated error handling in TeamAgentToolsInstaller to provide clearer guidance on available domains, improving user experience.
- Added an important note in TeamProvisioningService documentation regarding domain support, clarifying that "member" is not a valid domain.
- Adjusted EditTeamDialog component for consistent styling by removing unnecessary class properties.

Made-with: Cursor
This commit is contained in:
iliya 2026-03-03 17:00:25 +02:00
parent 0e627c4b3f
commit faf042d640
4 changed files with 42 additions and 2 deletions

View file

@ -1206,7 +1206,7 @@ async function main() {
die('Unknown process action: ' + String(action));
}
die('Unknown domain: ' + String(domain));
die('Unknown domain: ' + String(domain) + '. Available domains: task, kanban, review, message, process. Run with --help for usage.');
}
main().catch((err) => {

View file

@ -423,6 +423,8 @@ function buildTeamCtlOpsInstructions(teamName: string, leadName: string): string
` - Do NOT split when work is inherently sequential, requires one person to keep consistent context, or the overhead would exceed the benefit.`,
` - When splitting, make each task have a clear completion criterion and a single accountable owner.`,
``,
`IMPORTANT: teamctl.js only supports these domains: task, kanban, review, message, process. There is NO "member" domain — team members are managed by spawning teammates via the Task tool, not via teamctl.`,
``,
`Task board operations — use teamctl.js via Bash:`,
`- Create task: node "$HOME/.claude/tools/teamctl.js" --team "${teamName}" task create --subject "..." --description "..." --owner "<actual-member-name>" --notify --from "${leadName}"`,
`- Assign/reassign owner: node "$HOME/.claude/tools/teamctl.js" --team "${teamName}" task set-owner <id> <member-name> --notify --from "${leadName}"`,

View file

@ -124,7 +124,7 @@ export const EditTeamDialog = ({
return (
<Dialog open={open} onOpenChange={(nextOpen) => !nextOpen && onClose()}>
<DialogContent className="max-w-2xl sm:max-w-md">
<DialogContent className="max-w-2xl">
<DialogHeader>
<DialogTitle>Edit Team</DialogTitle>
<DialogDescription>Change team name, description and color</DialogDescription>

View file

@ -8,6 +8,9 @@ import { getWorktreeNavigationState } from '../utils/stateResetHelpers';
const logger = createLogger('teamSlice');
const TEAM_GET_DATA_TIMEOUT_MS = 30_000;
function nowIso(): string {
return new Date().toISOString();
}
function withTimeout<T>(promise: Promise<T>, ms: number, label: string): Promise<T> {
let timer: ReturnType<typeof setTimeout> | undefined;
@ -138,6 +141,11 @@ export interface TeamSlice {
lastSendMessageResult: SendMessageResult | null;
reviewActionError: string | null;
provisioningRuns: Record<string, TeamProvisioningProgress>;
/**
* Per-team lower bound for provisioning progress timestamps.
* Used to ignore late progress events from a previous run after stoplaunch.
*/
provisioningStartedAtFloorByTeam: Record<string, string>;
leadActivityByTeam: Record<string, LeadActivityState>;
activeProvisioningRunId: string | null;
provisioningError: string | null;
@ -231,6 +239,7 @@ export const createTeamSlice: StateCreator<AppState, [], [], TeamSlice> = (set,
lastSendMessageResult: null,
reviewActionError: null,
provisioningRuns: {},
provisioningStartedAtFloorByTeam: {},
leadActivityByTeam: {},
activeProvisioningRunId: null,
provisioningError: null,
@ -746,6 +755,18 @@ export const createTeamSlice: StateCreator<AppState, [], [], TeamSlice> = (set,
},
createTeam: async (request: TeamCreateRequest) => {
// Ensure provisioning progress subscription is active (defensive).
get().subscribeProvisioningProgress();
// Establish a per-team floor so late events from a previous run can't override UI.
const floor = nowIso();
set((state) => ({
provisioningStartedAtFloorByTeam: {
...state.provisioningStartedAtFloorByTeam,
[request.teamName]: floor,
},
}));
// Clear stale provisioning runs for this team so the banner starts fresh
set((state) => {
const cleaned = { ...state.provisioningRuns };
@ -783,6 +804,18 @@ export const createTeamSlice: StateCreator<AppState, [], [], TeamSlice> = (set,
},
launchTeam: async (request: TeamLaunchRequest) => {
// Ensure provisioning progress subscription is active (defensive).
get().subscribeProvisioningProgress();
// Establish a per-team floor so late events from a previous run can't override UI.
const floor = nowIso();
set((state) => ({
provisioningStartedAtFloorByTeam: {
...state.provisioningStartedAtFloorByTeam,
[request.teamName]: floor,
},
}));
// Clear stale provisioning runs for this team so the banner starts fresh
set((state) => {
const cleaned = { ...state.provisioningRuns };
@ -826,6 +859,11 @@ export const createTeamSlice: StateCreator<AppState, [], [], TeamSlice> = (set,
},
onProvisioningProgress: (progress: TeamProvisioningProgress) => {
const floor = get().provisioningStartedAtFloorByTeam[progress.teamName];
if (floor && progress.startedAt < floor) {
// Ignore late progress from a previous run (common after stop→launch).
return;
}
set((state) => ({
provisioningRuns: {
...state.provisioningRuns,