diff --git a/src/main/services/team/TeamAgentToolsInstaller.ts b/src/main/services/team/TeamAgentToolsInstaller.ts index 6cd215c9..6d22580b 100644 --- a/src/main/services/team/TeamAgentToolsInstaller.ts +++ b/src/main/services/team/TeamAgentToolsInstaller.ts @@ -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) => { diff --git a/src/main/services/team/TeamProvisioningService.ts b/src/main/services/team/TeamProvisioningService.ts index eb83f854..464ec419 100644 --- a/src/main/services/team/TeamProvisioningService.ts +++ b/src/main/services/team/TeamProvisioningService.ts @@ -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 "" --notify --from "${leadName}"`, `- Assign/reassign owner: node "$HOME/.claude/tools/teamctl.js" --team "${teamName}" task set-owner --notify --from "${leadName}"`, diff --git a/src/renderer/components/team/dialogs/EditTeamDialog.tsx b/src/renderer/components/team/dialogs/EditTeamDialog.tsx index 97459018..6d2494bb 100644 --- a/src/renderer/components/team/dialogs/EditTeamDialog.tsx +++ b/src/renderer/components/team/dialogs/EditTeamDialog.tsx @@ -124,7 +124,7 @@ export const EditTeamDialog = ({ return ( !nextOpen && onClose()}> - + Edit Team Change team name, description and color diff --git a/src/renderer/store/slices/teamSlice.ts b/src/renderer/store/slices/teamSlice.ts index 6c6ad665..db0e31be 100644 --- a/src/renderer/store/slices/teamSlice.ts +++ b/src/renderer/store/slices/teamSlice.ts @@ -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(promise: Promise, ms: number, label: string): Promise { let timer: ReturnType | undefined; @@ -138,6 +141,11 @@ export interface TeamSlice { lastSendMessageResult: SendMessageResult | null; reviewActionError: string | null; provisioningRuns: Record; + /** + * Per-team lower bound for provisioning progress timestamps. + * Used to ignore late progress events from a previous run after stop→launch. + */ + provisioningStartedAtFloorByTeam: Record; leadActivityByTeam: Record; activeProvisioningRunId: string | null; provisioningError: string | null; @@ -231,6 +239,7 @@ export const createTeamSlice: StateCreator = (set, lastSendMessageResult: null, reviewActionError: null, provisioningRuns: {}, + provisioningStartedAtFloorByTeam: {}, leadActivityByTeam: {}, activeProvisioningRunId: null, provisioningError: null, @@ -746,6 +755,18 @@ export const createTeamSlice: StateCreator = (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 = (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 = (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,